├── .github └── workflows │ └── flatpak.yml ├── .gitignore ├── COPYING ├── README.md ├── data ├── icons │ ├── hicolor │ │ ├── scalable │ │ │ └── apps │ │ │ │ └── io.github.vmkspv.netsleuth.svg │ │ └── symbolic │ │ │ └── apps │ │ │ └── io.github.vmkspv.netsleuth-symbolic.svg │ └── meson.build ├── io.github.vmkspv.netsleuth.SearchProvider.desktop.in ├── io.github.vmkspv.netsleuth.SearchProvider.service.in ├── io.github.vmkspv.netsleuth.desktop.in ├── io.github.vmkspv.netsleuth.metainfo.xml.in ├── io.github.vmkspv.netsleuth.search-provider.ini ├── meson.build └── screenshots │ ├── main-1.png │ ├── main-2.png │ ├── main-3.png │ └── preview.png ├── io.github.vmkspv.netsleuth.json ├── meson.build ├── netsleuth.doap ├── po ├── LINGUAS ├── POTFILES ├── README.md ├── bg.po ├── it.po ├── ja.po ├── meson.build ├── netsleuth.pot ├── pt_BR.po ├── ru.po ├── uk.po └── update-pot.sh └── src ├── __init__.py ├── calculator.py ├── cmdline.py ├── main.py ├── meson.build ├── netsleuth.gresource.xml ├── netsleuth.in ├── netsleuth_search_provider.in ├── window.py └── window.ui /.github/workflows/flatpak.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: CI 6 | jobs: 7 | flatpak: 8 | name: "Flatpak" 9 | container: 10 | image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48 11 | options: --privileged 12 | strategy: 13 | matrix: 14 | variant: 15 | - arch: x86_64 16 | runner: ubuntu-24.04 17 | - arch: aarch64 18 | runner: ubuntu-24.04-arm 19 | runs-on: ${{ matrix.variant.runner }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Validate AppStream file 23 | run: | 24 | appstreamcli validate --no-net data/io.github.vmkspv.netsleuth.metainfo.xml.in 25 | - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 26 | with: 27 | bundle: netsleuth.flatpak 28 | manifest-path: io.github.vmkspv.netsleuth.json 29 | cache-key: flatpak-builder-${{ github.sha }} 30 | arch: ${{ matrix.variant.arch }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .flatpak 2 | .flatpak-builder 3 | .vscode 4 | _build -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Netsleuth 4 | 5 | _Netsleuth_ is a simple utility for calculating and analyzing IP subnet values. 6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

18 | 19 |

20 | 21 |

22 | 23 | ## Installation 24 | 25 | The recommended installation method is via Flatpak. 26 | 27 |

28 | 29 | 30 | 31 |

32 | 33 | To use the command-line interface, add a shell alias by running the following command: 34 | 35 | ```bash 36 | echo "alias netsleuth='flatpak run io.github.vmkspv.netsleuth'" >> ~/.bashrc 37 | ``` 38 | 39 |
40 | Command-line interface 41 | 42 | ``` 43 | > netsleuth --help 44 | usage: netsleuth [ip_address] [-m MASK] [--binary] [--hex] [-h] [-v] 45 | 46 | positional arguments: 47 | ip_address ip for calculation 48 | 49 | options: 50 | -m MASK, --mask MASK subnet mask (default: 24) 51 | --binary show binary values 52 | --hex show hexadecimal values 53 | 54 | general: 55 | -h, --help show this help message and exit 56 | -v, --version show version information and exit 57 | ``` 58 | 59 |
60 | 61 | ## Building from source 62 | 63 | ### GNOME Builder 64 | 65 | The recommended method is to use GNOME Builder: 66 | 67 | 1. Install [`org.gnome.Builder`](https://gitlab.gnome.org/GNOME/gnome-builder) from Flathub. 68 | 2. Open Builder and select `Clone Repository`. 69 | 3. Clone `https://github.com/vmkspv/netsleuth.git`. 70 | 4. Press `Run Project` at the top once project is loaded. 71 | 72 | ### Flatpak 73 | 74 | You can also build the actual code as Flatpak: 75 | 76 | 1. Install [`org.flatpak.Builder`](https://github.com/flatpak/flatpak-builder) from Flathub. 77 | 2. Clone `https://github.com/vmkspv/netsleuth.git` and `cd netsleuth`. 78 | 3. Run `flatpak run org.flatpak.Builder --install --user --force-clean build-dir io.github.vmkspv.netsleuth.json`. 79 | 80 | ## Contributing 81 | 82 | Contributions are welcome! 83 | 84 | If you have an idea, bug report or something else, don’t hesitate to [open an issue](https://github.com/vmkspv/netsleuth/issues). 85 | 86 | > This project follows the [GNOME Code of Conduct](https://conduct.gnome.org). 87 | 88 | ## License 89 | 90 | Netsleuth is released under the [GPL-3.0 license](COPYING). 91 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/apps/io.github.vmkspv.netsleuth.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | Netsleuth Scalable Icon 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /data/icons/hicolor/symbolic/apps/io.github.vmkspv.netsleuth-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | Netsleuth Symbolic Icon 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /data/icons/meson.build: -------------------------------------------------------------------------------- 1 | application_id = 'io.github.vmkspv.netsleuth' 2 | 3 | scalable_dir = 'hicolor' / 'scalable' / 'apps' 4 | install_data( 5 | scalable_dir / ('@0@.svg').format(application_id), 6 | install_dir: get_option('datadir') / 'icons' / scalable_dir 7 | ) 8 | 9 | symbolic_dir = 'hicolor' / 'symbolic' / 'apps' 10 | install_data( 11 | symbolic_dir / ('@0@-symbolic.svg').format(application_id), 12 | install_dir: get_option('datadir') / 'icons' / symbolic_dir 13 | ) -------------------------------------------------------------------------------- /data/io.github.vmkspv.netsleuth.SearchProvider.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Netsleuth Search Provider 3 | Icon=io.github.vmkspv.netsleuth 4 | Type=Service 5 | DBusActivatable=true 6 | NoDisplay=true 7 | X-GNOME-SearchProvider-Prefix=io.github.vmkspv.netsleuth 8 | X-GNOME-SearchProvider-Path=/io/github/vmkspv/netsleuth/SearchProvider -------------------------------------------------------------------------------- /data/io.github.vmkspv.netsleuth.SearchProvider.service.in: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=@appid@.SearchProvider 3 | Exec=@bindir@/netsleuth_search_provider -------------------------------------------------------------------------------- /data/io.github.vmkspv.netsleuth.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | # Translators: Netsleuth is the application name. Do NOT translate or transliterate this string! 3 | Name=Netsleuth 4 | GenericName=IP Subnet Calculator 5 | Exec=netsleuth 6 | Icon=io.github.vmkspv.netsleuth 7 | Terminal=false 8 | Type=Application 9 | Categories=GTK;Utility; 10 | # Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! 11 | Keywords=Network;IP;Subnet;Mask;Host;Calculator; 12 | StartupNotify=true 13 | X-Purism-FormFactor=Workstation;Mobile; -------------------------------------------------------------------------------- /data/io.github.vmkspv.netsleuth.metainfo.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | CC-BY-SA-4.0 4 | GPL-3.0-or-later 5 | io.github.vmkspv.netsleuth 6 | Netsleuth 7 | Calculate IP subnets 8 | 9 |

A simple utility for the calculation and analysis of IP subnet values, designed to simplify network configuration tasks.

10 |
11 | 12 | 13 | https://raw.githubusercontent.com/vmkspv/netsleuth/main/data/screenshots/main-1.png 14 | Calculation options 15 | 16 | 17 | https://raw.githubusercontent.com/vmkspv/netsleuth/main/data/screenshots/main-2.png 18 | List of results 19 | 20 | 21 | https://raw.githubusercontent.com/vmkspv/netsleuth/main/data/screenshots/main-3.png 22 | Split view layout 23 | 24 | 25 | 26 | #6AA698 27 | #1E4C42 28 | 29 | 30 | [(106, 166, 152), (30, 76, 66)] 31 | 32 | https://github.com/vmkspv/netsleuth 33 | https://github.com/vmkspv/netsleuth/issues 34 | https://github.com/vmkspv/netsleuth 35 | https://github.com/vmkspv/netsleuth/tree/main/po 36 | 37 | Vladimir Kosolapov 38 | 39 | 40 | keyboard 41 | pointing 42 | touch 43 | 44 | 45 | offline-only 46 | 360 47 | 48 | 49 | Utility 50 | 51 | io.github.vmkspv.netsleuth.desktop 52 | netsleuth 53 | 54 | 55 | 56 |
    57 |
  • Updated to GNOME 48 runtime
  • 58 |
  • Minor code optimization/fixes
  • 59 |
60 |

Updated/added translations:

61 |
    62 |
  • Japanese (Ryo Nakano)
  • 63 |
64 |
65 |
66 | 67 | 68 |
    69 |
  • Added CLI support
  • 70 |
  • Implemented search provider
  • 71 |
  • Added IPv4/IPv6 mappings
  • 72 |
  • Improved about dialog
  • 73 |
  • Added multi-window support
  • 74 |
75 |

Updated/added translations:

76 |
    77 |
  • Bulgarian (twlvnn kraftwerk)
  • 78 |
  • Italian (Albano Battistella)
  • 79 |
80 |
81 |
82 | 83 | 84 |
    85 |
  • Added netmask search
  • 86 |
  • Implemented split view
  • 87 |
  • Added hexadecimal output
  • 88 |
89 |
90 |
91 | 92 | 93 |
    94 |
  • Added export options
  • 95 |
  • Improved keyboard navigation
  • 96 |
  • Minor code optimization/fixes
  • 97 |
98 |
99 |
100 | 101 | 102 |
    103 |
  • Improved binary output
  • 104 |
  • Added interesting facts
  • 105 |
  • Implemented history saving
  • 106 |
107 |

Updated/added translations:

108 |
    109 |
  • Brazilian Portuguese (Kelvin Novais)
  • 110 |
111 |
112 |
113 | 114 | 115 |
    116 |
  • Added host exponent
  • 117 |
  • Added PTR generation
  • 118 |
  • Added button tooltips
  • 119 |
  • Minor code optimization
  • 120 |
121 |

Updated/added translations:

122 |
    123 |
  • Bulgarian (twlvnn kraftwerk)
  • 124 |
125 |
126 |
127 | 128 | 129 |
    130 |
  • Optimized IP lookup
  • 131 |
  • Improved input validation
  • 132 |
  • Added translator credits
  • 133 |
134 |

Updated/added translations:

135 |
    136 |
  • Italian (Albano Battistella)
  • 137 |
138 |
139 |
140 | 141 | 142 |

Initial release!

143 |
144 |
145 |
146 | 147 |
148 | -------------------------------------------------------------------------------- /data/io.github.vmkspv.netsleuth.search-provider.ini: -------------------------------------------------------------------------------- 1 | [Shell Search Provider] 2 | DesktopId=@appid@.desktop 3 | BusName=@appid@.SearchProvider 4 | ObjectPath=@object_path@ 5 | Version=2 -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | application_id = 'io.github.vmkspv.netsleuth' 2 | 3 | desktop_file = i18n.merge_file( 4 | input: 'io.github.vmkspv.netsleuth.desktop.in', 5 | output: 'io.github.vmkspv.netsleuth.desktop', 6 | type: 'desktop', 7 | po_dir: '../po', 8 | install: true, 9 | install_dir: get_option('datadir') / 'applications' 10 | ) 11 | 12 | searchprovider_desktop_file = i18n.merge_file( 13 | input: 'io.github.vmkspv.netsleuth.SearchProvider.desktop.in', 14 | output: 'io.github.vmkspv.netsleuth.SearchProvider.desktop', 15 | type: 'desktop', 16 | po_dir: '../po', 17 | install: true, 18 | install_dir: get_option('datadir') / 'applications' 19 | ) 20 | 21 | desktop_utils = find_program('desktop-file-validate', required: false) 22 | if desktop_utils.found() 23 | test('Validate desktop file', desktop_utils, args: [desktop_file]) 24 | test('Validate search provider desktop file', desktop_utils, args: [searchprovider_desktop_file]) 25 | endif 26 | 27 | appstream_file = i18n.merge_file( 28 | input: 'io.github.vmkspv.netsleuth.metainfo.xml.in', 29 | output: 'io.github.vmkspv.netsleuth.metainfo.xml', 30 | po_dir: '../po', 31 | install: true, 32 | install_dir: get_option('datadir') / 'metainfo' 33 | ) 34 | 35 | appstreamcli = find_program('appstreamcli', required: false, disabler: true) 36 | test('Validate appstream file', appstreamcli, 37 | args: ['validate', '--no-net', '--explain', appstream_file]) 38 | 39 | service_conf = configuration_data() 40 | service_conf.set('appid', application_id) 41 | service_conf.set('name', meson.project_name()) 42 | service_conf.set('bindir', join_paths(get_option('prefix'), get_option('bindir'))) 43 | configure_file( 44 | input: '@0@.SearchProvider.service.in'.format(application_id), 45 | output: '@0@.SearchProvider.service'.format(application_id), 46 | configuration: service_conf, 47 | install_dir: get_option('datadir') / 'dbus-1' / 'services' 48 | ) 49 | 50 | search_conf = configuration_data() 51 | search_conf.set('appid', application_id) 52 | search_conf.set('object_path', '/io/github/vmkspv/netsleuth/SearchProvider') 53 | configure_file( 54 | input: '@0@.search-provider.ini'.format(application_id), 55 | output: '@0@.search-provider.ini'.format(application_id), 56 | configuration: search_conf, 57 | install_dir: get_option('datadir') / 'gnome-shell' / 'search-providers' 58 | ) 59 | 60 | subdir('icons') -------------------------------------------------------------------------------- /data/screenshots/main-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmkspv/netsleuth/2943c713088b6903207e09aca1d39b71cc3541cd/data/screenshots/main-1.png -------------------------------------------------------------------------------- /data/screenshots/main-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmkspv/netsleuth/2943c713088b6903207e09aca1d39b71cc3541cd/data/screenshots/main-2.png -------------------------------------------------------------------------------- /data/screenshots/main-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmkspv/netsleuth/2943c713088b6903207e09aca1d39b71cc3541cd/data/screenshots/main-3.png -------------------------------------------------------------------------------- /data/screenshots/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmkspv/netsleuth/2943c713088b6903207e09aca1d39b71cc3541cd/data/screenshots/preview.png -------------------------------------------------------------------------------- /io.github.vmkspv.netsleuth.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "io.github.vmkspv.netsleuth", 3 | "runtime" : "org.gnome.Platform", 4 | "runtime-version" : "48", 5 | "sdk" : "org.gnome.Sdk", 6 | "command" : "netsleuth", 7 | "finish-args" : [ 8 | "--share=ipc", 9 | "--socket=wayland", 10 | "--socket=fallback-x11", 11 | "--device=dri", 12 | "--own-name=io.github.vmkspv.netsleuth.SearchProvider" 13 | ], 14 | "modules" : [ 15 | { 16 | "name" : "netsleuth", 17 | "buildsystem" : "meson", 18 | "sources" : [ 19 | { 20 | "type" : "dir", 21 | "path" : "." 22 | } 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('netsleuth', 2 | version: '1.1.1', 3 | meson_version: '>= 0.62.0', 4 | default_options: ['warning_level=2', 'werror=false'] 5 | ) 6 | 7 | i18n = import('i18n') 8 | gnome = import('gnome') 9 | 10 | subdir('data') 11 | subdir('src') 12 | subdir('po') 13 | 14 | gnome.post_install( 15 | gtk_update_icon_cache: true, 16 | update_desktop_database: true 17 | ) -------------------------------------------------------------------------------- /netsleuth.doap: -------------------------------------------------------------------------------- 1 | 2 | 7 | Netsleuth 8 | Calculate IP subnets 9 | A simple utility for the calculation and analysis of IP subnet values, designed to simplify network configuration tasks. 10 | 11 | 12 | 13 | Python 14 | GTK 4 15 | Libadwaita 16 | 17 | 18 | Vladimir Kosolapov 19 | 20 | 21 | 22 | vmkspv 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | # Please keep this list alphabetically sorted 2 | bg 3 | it 4 | ja 5 | pt_BR 6 | ru 7 | uk -------------------------------------------------------------------------------- /po/POTFILES: -------------------------------------------------------------------------------- 1 | data/io.github.vmkspv.netsleuth.desktop.in 2 | data/io.github.vmkspv.netsleuth.metainfo.xml.in 3 | src/calculator.py 4 | src/cmdline.py 5 | src/window.py 6 | src/window.ui -------------------------------------------------------------------------------- /po/README.md: -------------------------------------------------------------------------------- 1 | # Translating 2 | 3 | ## New translations 4 | 5 | To create a new translation for Netsleuth: 6 | 7 | 1. Use a [PO file](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) editor of your choice. 8 | 2. Create a new translation based on the [`netsleuth.pot`](netsleuth.pot) file. 9 | 3. Select the target language for the new translation. 10 | 4. Translate all strings in the editor. 11 | 5. Save the file as `.po` in the `po` directory. 12 | 6. Add the language code to the [`LINGUAS`](LINGUAS) file. 13 | 14 | ## Updating translations 15 | 16 | When new strings for translation appear in the source code: 17 | 18 | 1. Run the POT file [update script](update-pot.sh): 19 | ``` 20 | ./po/update-pot.sh 21 | ``` 22 | 2. Open the existing `.po` file in your editor. 23 | 3. Update the translation from the new POT file. 24 | 4. Translate new strings and review existing translations. 25 | 5. Save the updated `.po` file. 26 | 27 | ## Submitting changes 28 | 29 | After creating a new translation or updating an existing one: 30 | 31 | 1. Ensure all changes are saved. 32 | 2. Create a new branch in your fork of the repository. 33 | 3. Commit the changes to the `.po` file and `LINGUAS`. 34 | 4. Push the changes to your fork on GitHub. 35 | 5. Create a pull request to the Netsleuth repository. 36 | 37 | Thank you for translating this project! 38 | -------------------------------------------------------------------------------- /po/bg.po: -------------------------------------------------------------------------------- 1 | # Bulgarian translations for Netsleuth package. 2 | # Copyright (C) 2024-2025 Vladimir Kosolapov 3 | # This file is distributed under the same license as the Netsleuth package. 4 | # twlvnn kraftwerk , 2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: netsleuth\n" 9 | "Report-Msgid-Bugs-To: https://github.com/vmkspv/netsleuth/issues\n" 10 | "POT-Creation-Date: 2024-12-13 16:26+0200\n" 11 | "PO-Revision-Date: 2025-04-27 13:00+0300\n" 12 | "Last-Translator: twlvnn kraftwerk \n" 13 | "Language-Team: Bulgarian \n" 14 | "Language: bg\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 19 | "X-Generator: Gtranslator 47.0\n" 20 | 21 | #. Translators: Netsleuth is the application name. Do NOT translate or transliterate this string! 22 | #: data/io.github.vmkspv.netsleuth.desktop.in:3 23 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:6 24 | msgid "Netsleuth" 25 | msgstr "Netsleuth" 26 | 27 | #: data/io.github.vmkspv.netsleuth.desktop.in:4 src/window.ui:33 28 | msgid "IP Subnet Calculator" 29 | msgstr "Калкулатор на IP адреси за подмрежи" 30 | 31 | #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! 32 | #: data/io.github.vmkspv.netsleuth.desktop.in:11 33 | msgid "Network;IP;Subnet;Mask;Host;Calculator;" 34 | msgstr "мрежа;подмрежа;маска;хост;калкулатор;" 35 | 36 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:7 37 | msgid "Calculate IP subnets" 38 | msgstr "Изчисляване на IP адеси за подмрежи" 39 | 40 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:9 src/cmdline.py:33 41 | msgid "" 42 | "A simple utility for the calculation and analysis of IP subnet values, " 43 | "designed to simplify network configuration tasks." 44 | msgstr "" 45 | "Проста програма за изчисляване и анализ на стойностите на IP адресите на " 46 | "подмрежите, предназначена за опростяване на задачите за настройване на мрежи." 47 | 48 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:14 49 | msgid "Calculation options" 50 | msgstr "Опции за изчисляване" 51 | 52 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:18 53 | msgid "List of results" 54 | msgstr "Списък на резултатите" 55 | 56 | #: src/calculator.py:41 src/window.py:477 57 | msgid "Address" 58 | msgstr "Адрес" 59 | 60 | #: src/calculator.py:42 src/window.py:478 61 | msgid "Netmask" 62 | msgstr "Мрежова маска" 63 | 64 | #: src/calculator.py:43 src/window.py:479 65 | msgid "Wildcard" 66 | msgstr "Заместваща маска" 67 | 68 | #: src/calculator.py:44 src/window.py:480 69 | msgid "Network" 70 | msgstr "Мрежа" 71 | 72 | #: src/calculator.py:45 src/window.py:481 73 | msgid "Broadcast" 74 | msgstr "Разпръскване" 75 | 76 | #: src/calculator.py:46 src/window.py:482 77 | msgid "First Host" 78 | msgstr "Първи хост" 79 | 80 | #: src/calculator.py:47 src/window.py:483 81 | msgid "Last Host" 82 | msgstr "Последен хост" 83 | 84 | #: src/calculator.py:48 src/window.py:138 src/window.py:484 85 | msgid "Total Hosts" 86 | msgstr "Общо хостове" 87 | 88 | #: src/calculator.py:49 src/window.py:485 89 | msgid "Category" 90 | msgstr "Категория" 91 | 92 | #: src/calculator.py:50 src/window.py:486 93 | msgid "PTR Record" 94 | msgstr "PTR запис" 95 | 96 | #: src/calculator.py:51 src/window.py:487 97 | msgid "IPv4 Mapped Address" 98 | msgstr "Зададен IPv4 адрес" 99 | 100 | #: src/calculator.py:52 src/window.py:488 101 | msgid "6to4 Prefix" 102 | msgstr "6to4 представка" 103 | 104 | #: src/calculator.py:56 src/cmdline.py:152 105 | msgid "Error" 106 | msgstr "Грешка" 107 | 108 | #: src/calculator.py:56 src/cmdline.py:152 109 | msgid "Invalid IP address or mask" 110 | msgstr "Неправилен IP адрес или маска" 111 | 112 | #: src/calculator.py:79 src/window.py:489 113 | msgid "Private (Class A)" 114 | msgstr "Частен (Клас A)" 115 | 116 | #: src/calculator.py:80 src/window.py:490 117 | msgid "Private (Class B)" 118 | msgstr "Частен (Клас B)" 119 | 120 | #: src/calculator.py:81 src/window.py:491 121 | msgid "Private (Class C)" 122 | msgstr "Частен (Клас C)" 123 | 124 | #: src/calculator.py:82 src/window.py:492 125 | msgid "Loopback" 126 | msgstr "Локален интерфейс" 127 | 128 | #: src/calculator.py:83 src/window.py:493 129 | msgid "Link-Local (APIPA)" 130 | msgstr "Локална връзка (APIPA)" 131 | 132 | #: src/calculator.py:84 src/window.py:494 133 | msgid "Multicast" 134 | msgstr "Мултикаст" 135 | 136 | #: src/calculator.py:85 src/window.py:495 137 | msgid "Reserved" 138 | msgstr "Запазен" 139 | 140 | #: src/calculator.py:88 src/window.py:496 141 | msgid "Public" 142 | msgstr "Публичен" 143 | 144 | #: src/cmdline.py:39 145 | msgid "ip for calculation" 146 | msgstr "ip адрес за изчисляване" 147 | 148 | #: src/cmdline.py:45 149 | msgid "subnet mask (default: 24)" 150 | msgstr "маска на подмрежата (стандартно: 24)" 151 | 152 | #: src/cmdline.py:50 153 | msgid "show binary values" 154 | msgstr "извеждане на двоични стойности" 155 | 156 | #: src/cmdline.py:55 157 | msgid "show hexadecimal values" 158 | msgstr "показване на шестнайсетични стойности" 159 | 160 | #: src/cmdline.py:61 161 | msgid "show this help message and exit" 162 | msgstr "извеждане на това съобщение за помощ и изход" 163 | 164 | #: src/cmdline.py:66 165 | msgid "show version information and exit" 166 | msgstr "извеждане на версията и изход" 167 | 168 | #: src/cmdline.py:119 169 | #, python-brace-format 170 | msgid "" 171 | "netsleuth {version}\n" 172 | "Copyright (C) 2024-2025 Vladimir Kosolapov\n" 173 | "License GPLv3+: GNU GPL version 3 or later .\n" 175 | "This is free software: you are free to change and redistribute it.\n" 176 | "There is NO WARRANTY, to the extent permitted by law.\n" 177 | "\n" 178 | "Please report bugs to: ." 179 | msgstr "" 180 | "netsleuth {version}\n" 181 | "Авторски права (C) 2024-2025 Vladimir Kosolapov\n" 182 | "Лицензия GPLv3+: GNU GPL версия 3 или по-нова .\n" 184 | "Това е свободен софтуер: може свободно да го променяте и разпространявате.\n" 185 | "Няма никаква ГАРАНЦИЯ, доколкото това е позволено от закона.\n" 186 | "\n" 187 | "Докладвайте за грешки на: ." 188 | 189 | #: src/cmdline.py:144 190 | msgid "options" 191 | msgstr "опции" 192 | 193 | #: src/cmdline.py:145 194 | msgid "general" 195 | msgstr "общи" 196 | 197 | #: src/cmdline.py:148 198 | msgid "positional arguments" 199 | msgstr "позиционни аргументи" 200 | 201 | #: src/cmdline.py:149 202 | msgid "usage:" 203 | msgstr "употреба:" 204 | 205 | #: src/window.py:91 206 | msgid "" 207 | "The 0.0.0.0 address is used to represent the default route or an unknown " 208 | "target network." 209 | msgstr "" 210 | "Адресът 0.0.0.0 се използва за представяне на стандартния маршрут или на " 211 | "неизвестна целева мрежа." 212 | 213 | #: src/window.py:92 214 | msgid "" 215 | "A PTR record enables reverse DNS lookup, translating an IP address back to a " 216 | "domain name." 217 | msgstr "" 218 | "PTR записите позволяват обратното DNS търсене, като превежда IP адрес " 219 | "обратно към име на домейн." 220 | 221 | #: src/window.py:93 222 | msgid "" 223 | "The 240.0.0.0/4 block was originally reserved for future experiments, but is " 224 | "now considered legacy." 225 | msgstr "" 226 | "240.0.0.0/4 първоначално е бил запазен за бъдещи експерименти, но сега се " 227 | "счита за остарял." 228 | 229 | #: src/window.py:94 230 | msgid "" 231 | "In a /30 subnet, there are only 2 usable IP addresses, often used for point-" 232 | "to-point links." 233 | msgstr "" 234 | "В /30 подмрежа има само 2 използваеми IP адреса, които често се използват за " 235 | "връзки от точка до точка." 236 | 237 | #: src/window.py:95 238 | msgid "" 239 | "Wildcard mask inverts subnet masks, providing flexible IP filtering in " 240 | "access lists." 241 | msgstr "" 242 | "Заместваща маска обръща маските на подмрежите, като осигурява гъвкаво " 243 | "филтриране на IP адреси в списъците за достъп." 244 | 245 | #: src/window.py:96 246 | msgid "" 247 | "An IP's binary representation always has 32 bits, regardless of the decimal " 248 | "notation used." 249 | msgstr "" 250 | "Двоичното представяне на IP адрес винаги има 32 бита, независимо от " 251 | "използваната десетична нотация." 252 | 253 | #: src/window.py:147 254 | msgid "Copy" 255 | msgstr "Копиране" 256 | 257 | #: src/window.py:193 src/window.py:407 258 | msgid "Copied to clipboard" 259 | msgstr "Копирано в буфера за обмен" 260 | 261 | #: src/window.py:245 src/window.ui:69 262 | msgid "History" 263 | msgstr "История" 264 | 265 | #: src/window.py:256 266 | msgid "Clear" 267 | msgstr "Изчистване" 268 | 269 | #: src/window.py:271 270 | msgid "No History" 271 | msgstr "Няма история" 272 | 273 | #: src/window.py:272 274 | msgid "Your calculation history will appear here" 275 | msgstr "Историята на изчисленията ви ще се появи тук" 276 | 277 | #: src/window.py:331 278 | msgid "Select" 279 | msgstr "Избор" 280 | 281 | #: src/window.py:346 src/window.py:389 282 | #, python-brace-format 283 | msgid "Calculated: {ip}/{mask}" 284 | msgstr "Изчислено: {ip}/{mask}" 285 | 286 | #: src/window.py:352 287 | msgid "History cleared" 288 | msgstr "Историята е изчистена" 289 | 290 | #: src/window.py:436 291 | msgid "Export results" 292 | msgstr "Резултати от изнасянето" 293 | 294 | #: src/window.py:511 295 | #, python-brace-format 296 | msgid "Saved to {file}" 297 | msgstr "Запазено в {file}" 298 | 299 | #: src/window.ui:40 300 | msgid "About Netsleuth" 301 | msgstr "Относно Netsleuth" 302 | 303 | #: src/window.ui:62 304 | msgid "Details" 305 | msgstr "Подробности" 306 | 307 | #: src/window.ui:65 308 | msgid "IP Address" 309 | msgstr "IP адрес" 310 | 311 | #: src/window.ui:81 312 | msgid "Subnet Mask" 313 | msgstr "Маска на подмрежата" 314 | 315 | #: src/window.ui:88 316 | msgid "Show Binary" 317 | msgstr "Показване в двоичен код" 318 | 319 | #: src/window.ui:89 320 | msgid "Display binary representation of IP addresses" 321 | msgstr "Показване на двоично представяне на IP адреси" 322 | 323 | #: src/window.ui:94 324 | msgid "Show Hexadecimal" 325 | msgstr "Показване на шестнадесетична" 326 | 327 | #: src/window.ui:95 328 | msgid "Display hexadecimal representation of IP addresses" 329 | msgstr "Показване на шестнайсетично представяне на IP адреси" 330 | 331 | #: src/window.ui:107 332 | msgid "Calculate" 333 | msgstr "Изчисляване" 334 | 335 | #: src/window.ui:124 336 | msgid "Did you know?" 337 | msgstr "Знаете ли, че?" 338 | 339 | #: src/window.ui:131 src/window.ui:182 src/window.ui:189 src/window.ui:220 340 | msgid "Results" 341 | msgstr "Резултати" 342 | 343 | #: src/window.ui:140 src/window.ui:229 344 | msgid "Copy All" 345 | msgstr "Копиране на всичко" 346 | 347 | #: src/window.ui:151 src/window.ui:240 348 | msgid "Export" 349 | msgstr "Изнасяне" 350 | 351 | #: src/window.ui:200 352 | msgid "No Results" 353 | msgstr "Няма резултати" 354 | 355 | #: src/window.ui:201 356 | msgid "Your calculation results will appear here" 357 | msgstr "Резултатите от изчисленията ви ще се появят тук" -------------------------------------------------------------------------------- /po/it.po: -------------------------------------------------------------------------------- 1 | # Italian translations for Netsleuth package. 2 | # Copyright (C) 2024-2025 Vladimir Kosolapov 3 | # This file is distributed under the same license as the Netsleuth package. 4 | # Albano Battistella , 2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: netsleuth\n" 9 | "Report-Msgid-Bugs-To: https://github.com/vmkspv/netsleuth/issues\n" 10 | "POT-Creation-Date: 2024-12-13 17:07+0200\n" 11 | "PO-Revision-Date: 2025-04-27 13:00+0300\n" 12 | "Last-Translator: Albano Battistella \n" 13 | "Language-Team: Italian \n" 14 | "Language: it\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #. Translators: Netsleuth is the application name. Do NOT translate or transliterate this string! 20 | #: data/io.github.vmkspv.netsleuth.desktop.in:3 21 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:6 22 | msgid "Netsleuth" 23 | msgstr "Netsleuth" 24 | 25 | #: data/io.github.vmkspv.netsleuth.desktop.in:4 src/window.ui:33 26 | msgid "IP Subnet Calculator" 27 | msgstr "Calcolatrice della subnet IP" 28 | 29 | #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! 30 | #: data/io.github.vmkspv.netsleuth.desktop.in:11 31 | msgid "Network;IP;Subnet;Mask;Host;Calculator;" 32 | msgstr "Rete;IP;Subnet;Maschera;Host;Calcolatrice;" 33 | 34 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:7 35 | msgid "Calculate IP subnets" 36 | msgstr "Calcola le subnet IP" 37 | 38 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:9 src/cmdline.py:33 39 | msgid "" 40 | "A simple utility for the calculation and analysis of IP subnet values, " 41 | "designed to simplify network configuration tasks." 42 | msgstr "" 43 | "Una semplice utility per il calcolo e l'analisi dei valori di subnet IP, " 44 | "progettata per semplificare le attività di configurazione di rete." 45 | 46 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:14 47 | msgid "Calculation options" 48 | msgstr "Opzioni di calcolo" 49 | 50 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:18 51 | msgid "List of results" 52 | msgstr "Elenco dei risultati" 53 | 54 | #: src/calculator.py:41 src/window.py:477 55 | msgid "Address" 56 | msgstr "Indirizzo" 57 | 58 | #: src/calculator.py:42 src/window.py:478 59 | msgid "Netmask" 60 | msgstr "Maschera di rete" 61 | 62 | #: src/calculator.py:43 src/window.py:479 63 | msgid "Wildcard" 64 | msgstr "Carattere jolly" 65 | 66 | #: src/calculator.py:44 src/window.py:480 67 | msgid "Network" 68 | msgstr "Rete" 69 | 70 | #: src/calculator.py:45 src/window.py:481 71 | msgid "Broadcast" 72 | msgstr "Broadcast" 73 | 74 | #: src/calculator.py:46 src/window.py:482 75 | msgid "First Host" 76 | msgstr "Primo host" 77 | 78 | #: src/calculator.py:47 src/window.py:483 79 | msgid "Last Host" 80 | msgstr "Ultimo host" 81 | 82 | #: src/calculator.py:48 src/window.py:138 src/window.py:484 83 | msgid "Total Hosts" 84 | msgstr "Totale host" 85 | 86 | #: src/calculator.py:49 src/window.py:485 87 | msgid "Category" 88 | msgstr "Categoria" 89 | 90 | #: src/calculator.py:50 src/window.py:486 91 | msgid "PTR Record" 92 | msgstr "PTR Record" 93 | 94 | #: src/calculator.py:51 src/window.py:487 95 | msgid "IPv4 Mapped Address" 96 | msgstr "Indirizzo IPv4 mappato" 97 | 98 | #: src/calculator.py:52 src/window.py:488 99 | msgid "6to4 Prefix" 100 | msgstr "Prefisso 6to4" 101 | 102 | #: src/calculator.py:56 src/cmdline.py:152 103 | msgid "Error" 104 | msgstr "Errore" 105 | 106 | #: src/calculator.py:56 src/cmdline.py:152 107 | msgid "Invalid IP address or mask" 108 | msgstr "Indirizzo IP o maschera non validi" 109 | 110 | #: src/calculator.py:79 src/window.py:489 111 | msgid "Private (Class A)" 112 | msgstr "Privato (Classe A)" 113 | 114 | #: src/calculator.py:80 src/window.py:490 115 | msgid "Private (Class B)" 116 | msgstr "Privato (Classe B)" 117 | 118 | #: src/calculator.py:81 src/window.py:491 119 | msgid "Private (Class C)" 120 | msgstr "Privato (Classe C)" 121 | 122 | #: src/calculator.py:82 src/window.py:492 123 | msgid "Loopback" 124 | msgstr "Loopback" 125 | 126 | #: src/calculator.py:83 src/window.py:493 127 | msgid "Link-Local (APIPA)" 128 | msgstr "Link-Locale (APIPA)" 129 | 130 | #: src/calculator.py:84 src/window.py:494 131 | msgid "Multicast" 132 | msgstr "Multicast" 133 | 134 | #: src/calculator.py:85 src/window.py:495 135 | msgid "Reserved" 136 | msgstr "Riservato" 137 | 138 | #: src/calculator.py:88 src/window.py:496 139 | msgid "Public" 140 | msgstr "Pubblico" 141 | 142 | #: src/cmdline.py:39 143 | msgid "ip for calculation" 144 | msgstr "ip per calcolo" 145 | 146 | #: src/cmdline.py:45 147 | msgid "subnet mask (default: 24)" 148 | msgstr "maschera di sottorete (predefinita: 24)" 149 | 150 | #: src/cmdline.py:50 151 | msgid "show binary values" 152 | msgstr "mostra valori binari" 153 | 154 | #: src/cmdline.py:55 155 | msgid "show hexadecimal values" 156 | msgstr "mostra valori esadecimali" 157 | 158 | #: src/cmdline.py:61 159 | msgid "show this help message and exit" 160 | msgstr "mostra questo messaggio di aiuto ed esci" 161 | 162 | #: src/cmdline.py:66 163 | msgid "show version information and exit" 164 | msgstr "mostra informazioni sulla versione ed esci" 165 | 166 | #: src/cmdline.py:119 167 | #, python-brace-format 168 | msgid "" 169 | "netsleuth {version}\n" 170 | "Copyright (C) 2024-2025 Vladimir Kosolapov\n" 171 | "License GPLv3+: GNU GPL version 3 or later .\n" 173 | "This is free software: you are free to change and redistribute it.\n" 174 | "There is NO WARRANTY, to the extent permitted by law.\n" 175 | "\n" 176 | "Please report bugs to: ." 177 | msgstr "" 178 | "netsleuth {version}\n" 179 | "Copyright (C) 2024-2025 Vladimir Kosolapov\n" 180 | "Licenza GPLv3+: GNU GPL versione 3 o successiva .\n" 182 | "Questo è software libero: sei libero di modificarlo e ridistribuirlo.\n" 183 | "NON C'È ALCUNA GARANZIA, nella misura consentita dalla legge.\n" 184 | "\n" 185 | "Segnala i bug a: ." 186 | 187 | #: src/cmdline.py:144 188 | msgid "options" 189 | msgstr "opzioni" 190 | 191 | #: src/cmdline.py:145 192 | msgid "general" 193 | msgstr "generale" 194 | 195 | #: src/cmdline.py:148 196 | msgid "positional arguments" 197 | msgstr "argomenti posizionali" 198 | 199 | #: src/cmdline.py:149 200 | msgid "usage:" 201 | msgstr "uso:" 202 | 203 | #: src/window.py:91 204 | msgid "" 205 | "The 0.0.0.0 address is used to represent the default route or an unknown " 206 | "target network." 207 | msgstr "" 208 | "L'indirizzo 0.0.0.0 viene utilizzato per rappresentare il percorso predefinito o una " 209 | "rete di destinazione sconosciuta." 210 | 211 | #: src/window.py:92 212 | msgid "" 213 | "A PTR record enables reverse DNS lookup, translating an IP address back to a " 214 | "domain name." 215 | msgstr "" 216 | "Un record PTR consente la ricerca DNS inversa, traducendo un indirizzo IP in un " 217 | "nome di dominio." 218 | 219 | #: src/window.py:93 220 | msgid "" 221 | "The 240.0.0.0/4 block was originally reserved for future experiments, but is " 222 | "now considered legacy." 223 | msgstr "" 224 | "Il blocco 240.0.0.0/4 era originariamente riservato per esperimenti futuri, ma è " 225 | "ora considerato obsoleto." 226 | 227 | #: src/window.py:94 228 | msgid "" 229 | "In a /30 subnet, there are only 2 usable IP addresses, often used for point-" 230 | "to-point links." 231 | msgstr "" 232 | "In una subnet /30, ci sono solo 2 indirizzi IP utilizzabili, spesso usati per " 233 | "collegamenti punto-punto." 234 | 235 | #: src/window.py:95 236 | msgid "" 237 | "Wildcard mask inverts subnet masks, providing flexible IP filtering in " 238 | "access lists." 239 | msgstr "" 240 | "La maschera jolly inverte le maschere di sottorete, fornendo un filtraggio " 241 | "IP flessibile negli elenchi di accesso." 242 | 243 | #: src/window.py:96 244 | msgid "" 245 | "An IP's binary representation always has 32 bits, regardless of the decimal " 246 | "notation used." 247 | msgstr "" 248 | "La rappresentazione binaria di un IP ha sempre 32 bit, indipendentemente dalla " 249 | "notazione decimale utilizzata." 250 | 251 | #: src/window.py:147 252 | msgid "Copy" 253 | msgstr "Copia" 254 | 255 | #: src/window.py:193 src/window.py:407 256 | msgid "Copied to clipboard" 257 | msgstr "Copiato negli appunti" 258 | 259 | #: src/window.py:245 src/window.ui:69 260 | msgid "History" 261 | msgstr "Cronologia" 262 | 263 | #: src/window.py:256 264 | msgid "Clear" 265 | msgstr "Pulisci" 266 | 267 | #: src/window.py:271 268 | msgid "No History" 269 | msgstr "Nessuna cronologia" 270 | 271 | #: src/window.py:272 272 | msgid "Your calculation history will appear here" 273 | msgstr "La cronologia dei tuoi calcoli apparirà qui" 274 | 275 | #: src/window.py:331 276 | msgid "Select" 277 | msgstr "Seleziona" 278 | 279 | #: src/window.py:346 src/window.py:389 280 | #, python-brace-format 281 | msgid "Calculated: {ip}/{mask}" 282 | msgstr "Calcolato: {ip}/{mask}" 283 | 284 | #: src/window.py:352 285 | msgid "History cleared" 286 | msgstr "Cronologia cancellata" 287 | 288 | #: src/window.py:436 289 | msgid "Export results" 290 | msgstr "Esporta risultati" 291 | 292 | #: src/window.py:511 293 | #, python-brace-format 294 | msgid "Saved to {file}" 295 | msgstr "Salvato in {file}" 296 | 297 | #: src/window.ui:40 298 | msgid "About Netsleuth" 299 | msgstr "Informazioni su Netsleuth" 300 | 301 | #: src/window.ui:62 302 | msgid "Details" 303 | msgstr "Dettagli" 304 | 305 | #: src/window.ui:65 306 | msgid "IP Address" 307 | msgstr "Indirizzo IP" 308 | 309 | #: src/window.ui:81 310 | msgid "Subnet Mask" 311 | msgstr "Maschera di sottorete" 312 | 313 | #: src/window.ui:88 314 | msgid "Show Binary" 315 | msgstr "Mostra binario" 316 | 317 | #: src/window.ui:89 318 | msgid "Display binary representation of IP addresses" 319 | msgstr "Visualizza la rappresentazione binaria degli indirizzi IP" 320 | 321 | #: src/window.ui:94 322 | msgid "Show Hexadecimal" 323 | msgstr "Mostra esadecimale" 324 | 325 | #: src/window.ui:95 326 | msgid "Display hexadecimal representation of IP addresses" 327 | msgstr "Visualizza la rappresentazione esadecimale degli indirizzi IP" 328 | 329 | #: src/window.ui:107 330 | msgid "Calculate" 331 | msgstr "Calcola" 332 | 333 | #: src/window.ui:124 334 | msgid "Did you know?" 335 | msgstr "Lo sapevi?" 336 | 337 | #: src/window.ui:131 src/window.ui:182 src/window.ui:189 src/window.ui:220 338 | msgid "Results" 339 | msgstr "Risultati" 340 | 341 | #: src/window.ui:140 src/window.ui:229 342 | msgid "Copy All" 343 | msgstr "Copia tutto" 344 | 345 | #: src/window.ui:151 src/window.ui:240 346 | msgid "Export" 347 | msgstr "Esporta" 348 | 349 | #: src/window.ui:200 350 | msgid "No Results" 351 | msgstr "Nessun risultato" 352 | 353 | #: src/window.ui:201 354 | msgid "Your calculation results will appear here" 355 | msgstr "I risultati del tuo calcolo appariranno qui" -------------------------------------------------------------------------------- /po/ja.po: -------------------------------------------------------------------------------- 1 | # Japanese translations for Netsleuth package. 2 | # Copyright (C) 2024-2025 Vladimir Kosolapov 3 | # This file is distributed under the same license as the Netsleuth package. 4 | # Ryo Nakano , 2025. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: netsleuth\n" 9 | "Report-Msgid-Bugs-To: https://github.com/vmkspv/netsleuth/issues\n" 10 | "POT-Creation-Date: 2025-04-27 10:23+0900\n" 11 | "PO-Revision-Date: 2025-04-27 10:29+0900\n" 12 | "Last-Translator: Ryo Nakano \n" 13 | "Language-Team: \n" 14 | "Language: ja\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #. Translators: Netsleuth is the application name. Do NOT translate or transliterate this string! 20 | #: data/io.github.vmkspv.netsleuth.desktop.in:3 21 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:6 22 | msgid "Netsleuth" 23 | msgstr "Netsleuth" 24 | 25 | #: data/io.github.vmkspv.netsleuth.desktop.in:4 src/window.ui:33 26 | msgid "IP Subnet Calculator" 27 | msgstr "IP サブネット計算ツール" 28 | 29 | #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! 30 | #: data/io.github.vmkspv.netsleuth.desktop.in:11 31 | msgid "Network;IP;Subnet;Mask;Host;Calculator;" 32 | msgstr "" 33 | "Network;IP;Subnet;Mask;Host;Calculator;ネットワーク;サブネット;マスク;ホスト;" 34 | "計算;" 35 | 36 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:7 37 | msgid "Calculate IP subnets" 38 | msgstr "IP サブネットを計算します" 39 | 40 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:9 src/cmdline.py:33 41 | msgid "" 42 | "A simple utility for the calculation and analysis of IP subnet values, " 43 | "designed to simplify network configuration tasks." 44 | msgstr "" 45 | "IP サブネット値を計算・分析するための、シンプルなツールです。ネットワーク設定" 46 | "作業を簡単にすることを目的としています。" 47 | 48 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:14 49 | msgid "Calculation options" 50 | msgstr "計算オプション" 51 | 52 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:18 53 | msgid "List of results" 54 | msgstr "結果の一覧" 55 | 56 | #: src/calculator.py:41 src/window.py:477 57 | msgid "Address" 58 | msgstr "アドレス" 59 | 60 | #: src/calculator.py:42 src/window.py:478 61 | msgid "Netmask" 62 | msgstr "ネットマスク" 63 | 64 | #: src/calculator.py:43 src/window.py:479 65 | msgid "Wildcard" 66 | msgstr "ワイルドカード" 67 | 68 | #: src/calculator.py:44 src/window.py:480 69 | msgid "Network" 70 | msgstr "ネットワーク" 71 | 72 | #: src/calculator.py:45 src/window.py:481 73 | msgid "Broadcast" 74 | msgstr "ブロードキャスト" 75 | 76 | #: src/calculator.py:46 src/window.py:482 77 | msgid "First Host" 78 | msgstr "先頭ホストアドレス" 79 | 80 | #: src/calculator.py:47 src/window.py:483 81 | msgid "Last Host" 82 | msgstr "末尾ホストアドレス" 83 | 84 | #: src/calculator.py:48 src/window.py:138 src/window.py:484 85 | msgid "Total Hosts" 86 | msgstr "ホストアドレス数" 87 | 88 | #: src/calculator.py:49 src/window.py:485 89 | msgid "Category" 90 | msgstr "カテゴリー" 91 | 92 | #: src/calculator.py:50 src/window.py:486 93 | msgid "PTR Record" 94 | msgstr "PTR レコード" 95 | 96 | #: src/calculator.py:51 src/window.py:487 97 | msgid "IPv4 Mapped Address" 98 | msgstr "IPv4 射影アドレス" 99 | 100 | #: src/calculator.py:52 src/window.py:488 101 | msgid "6to4 Prefix" 102 | msgstr "6to4 プレフィックス" 103 | 104 | #: src/calculator.py:56 src/cmdline.py:152 105 | msgid "Error" 106 | msgstr "エラー" 107 | 108 | #: src/calculator.py:56 src/cmdline.py:152 109 | msgid "Invalid IP address or mask" 110 | msgstr "IP アドレスまたはネットマスクが不正です" 111 | 112 | #: src/calculator.py:79 src/window.py:489 113 | msgid "Private (Class A)" 114 | msgstr "プライベート (クラス A)" 115 | 116 | #: src/calculator.py:80 src/window.py:490 117 | msgid "Private (Class B)" 118 | msgstr "プライベート (クラス B)" 119 | 120 | #: src/calculator.py:81 src/window.py:491 121 | msgid "Private (Class C)" 122 | msgstr "プライベート (クラス C)" 123 | 124 | #: src/calculator.py:82 src/window.py:492 125 | msgid "Loopback" 126 | msgstr "ループバック" 127 | 128 | #: src/calculator.py:83 src/window.py:493 129 | msgid "Link-Local (APIPA)" 130 | msgstr "リンクローカル (APIPA)" 131 | 132 | #: src/calculator.py:84 src/window.py:494 133 | msgid "Multicast" 134 | msgstr "マルチキャスト" 135 | 136 | #: src/calculator.py:85 src/window.py:495 137 | msgid "Reserved" 138 | msgstr "予約" 139 | 140 | #: src/calculator.py:88 src/window.py:496 141 | msgid "Public" 142 | msgstr "パブリック" 143 | 144 | #: src/cmdline.py:39 145 | msgid "ip for calculation" 146 | msgstr "計算対象の IP アドレス" 147 | 148 | #: src/cmdline.py:45 149 | msgid "subnet mask (default: 24)" 150 | msgstr "サブネットマスク (デフォルト: 24)" 151 | 152 | #: src/cmdline.py:50 153 | msgid "show binary values" 154 | msgstr "二進数の値を表示する" 155 | 156 | #: src/cmdline.py:55 157 | msgid "show hexadecimal values" 158 | msgstr "十六進数の値を表示する" 159 | 160 | #: src/cmdline.py:61 161 | msgid "show this help message and exit" 162 | msgstr "使い方を表示して終了する" 163 | 164 | #: src/cmdline.py:66 165 | msgid "show version information and exit" 166 | msgstr "バージョン情報を表示して終了する" 167 | 168 | #: src/cmdline.py:119 169 | #, python-brace-format 170 | msgid "" 171 | "netsleuth {version}\n" 172 | "Copyright (C) 2024-2025 Vladimir Kosolapov\n" 173 | "License GPLv3+: GNU GPL version 3 or later .\n" 175 | "This is free software: you are free to change and redistribute it.\n" 176 | "There is NO WARRANTY, to the extent permitted by law.\n" 177 | "\n" 178 | "Please report bugs to: ." 179 | msgstr "" 180 | "netsleuth {version}\n" 181 | "Copyright (C) 2024-2025 Vladimir Kosolapov\n" 182 | "License GPLv3+: GNU GPL version 3 or later .\n" 184 | "This is free software: you are free to change and redistribute it.\n" 185 | "There is NO WARRANTY, to the extent permitted by law.\n" 186 | "\n" 187 | "バグ報告はこちら: " 188 | 189 | #: src/cmdline.py:144 190 | msgid "options" 191 | msgstr "オプション" 192 | 193 | #: src/cmdline.py:145 194 | msgid "general" 195 | msgstr "一般" 196 | 197 | #: src/cmdline.py:148 198 | msgid "positional arguments" 199 | msgstr "引数" 200 | 201 | #: src/cmdline.py:149 202 | msgid "usage:" 203 | msgstr "使用法:" 204 | 205 | #: src/window.py:91 206 | msgid "" 207 | "The 0.0.0.0 address is used to represent the default route or an unknown " 208 | "target network." 209 | msgstr "" 210 | "0.0.0.0 というアドレスは、デフォルトルートや宛先不明なネットワークを表すのに" 211 | "使われます。" 212 | 213 | #: src/window.py:92 214 | msgid "" 215 | "A PTR record enables reverse DNS lookup, translating an IP address back to a " 216 | "domain name." 217 | msgstr "" 218 | "PTR レコードは DNS の逆引きを可能にするもので、IP アドレスをドメイン名に変換" 219 | "します。" 220 | 221 | #: src/window.py:93 222 | msgid "" 223 | "The 240.0.0.0/4 block was originally reserved for future experiments, but is " 224 | "now considered legacy." 225 | msgstr "" 226 | "240.0.0.0/4 のブロックは、元々将来のための予約でしたが、今では過去の遺物とみ" 227 | "なされています。" 228 | 229 | #: src/window.py:94 230 | msgid "" 231 | "In a /30 subnet, there are only 2 usable IP addresses, often used for point-" 232 | "to-point links." 233 | msgstr "" 234 | "/30 のサブネットで利用可能な IP アドレスは 2 台のみなので、多くの場合ポイント" 235 | "ツーポイント接続に用いられます。" 236 | 237 | #: src/window.py:95 238 | msgid "" 239 | "Wildcard mask inverts subnet masks, providing flexible IP filtering in " 240 | "access lists." 241 | msgstr "" 242 | "ワイルドカードマスクはサブネットマスクを逆転させたもので、アクセス制御リスト" 243 | "において柔軟な IP フィルタリングを可能にします。" 244 | 245 | #: src/window.py:96 246 | msgid "" 247 | "An IP's binary representation always has 32 bits, regardless of the decimal " 248 | "notation used." 249 | msgstr "" 250 | "IP アドレスは常に 32 ビットの二進数で表現され、それは十進法で表記されていても" 251 | "変わりません。" 252 | 253 | #: src/window.py:147 254 | msgid "Copy" 255 | msgstr "コピー" 256 | 257 | #: src/window.py:193 src/window.py:407 258 | msgid "Copied to clipboard" 259 | msgstr "クリップボードにコピーしました" 260 | 261 | #: src/window.py:245 src/window.ui:69 262 | msgid "History" 263 | msgstr "履歴" 264 | 265 | #: src/window.py:256 266 | msgid "Clear" 267 | msgstr "消去" 268 | 269 | #: src/window.py:271 270 | msgid "No History" 271 | msgstr "履歴なし" 272 | 273 | #: src/window.py:272 274 | msgid "Your calculation history will appear here" 275 | msgstr "計算履歴がここに表示されます" 276 | 277 | #: src/window.py:331 278 | msgid "Select" 279 | msgstr "選択" 280 | 281 | #: src/window.py:346 src/window.py:389 282 | #, python-brace-format 283 | msgid "Calculated: {ip}/{mask}" 284 | msgstr "次の IP アドレスで計算しました: {ip}/{mask}" 285 | 286 | #: src/window.py:352 287 | msgid "History cleared" 288 | msgstr "履歴を消去しました" 289 | 290 | #: src/window.py:436 291 | msgid "Export results" 292 | msgstr "結果をエクスポート" 293 | 294 | #: src/window.py:511 295 | #, python-brace-format 296 | msgid "Saved to {file}" 297 | msgstr "{file} に保存しました" 298 | 299 | #: src/window.ui:40 300 | msgid "About Netsleuth" 301 | msgstr "Netsleuth について" 302 | 303 | #: src/window.ui:62 304 | msgid "Details" 305 | msgstr "詳細" 306 | 307 | #: src/window.ui:65 308 | msgid "IP Address" 309 | msgstr "IP アドレス" 310 | 311 | #: src/window.ui:81 312 | msgid "Subnet Mask" 313 | msgstr "サブネットマスク" 314 | 315 | #: src/window.ui:88 316 | msgid "Show Binary" 317 | msgstr "二進数の値を表示" 318 | 319 | #: src/window.ui:89 320 | msgid "Display binary representation of IP addresses" 321 | msgstr "二進数で表現した IP アドレスを表示します" 322 | 323 | #: src/window.ui:94 324 | msgid "Show Hexadecimal" 325 | msgstr "十六進数の値を表示" 326 | 327 | #: src/window.ui:95 328 | msgid "Display hexadecimal representation of IP addresses" 329 | msgstr "十六進数で表現した IP アドレスを表示します" 330 | 331 | #: src/window.ui:107 332 | msgid "Calculate" 333 | msgstr "計算する" 334 | 335 | #: src/window.ui:124 336 | msgid "Did you know?" 337 | msgstr "ご存じですか?" 338 | 339 | #: src/window.ui:131 src/window.ui:182 src/window.ui:189 src/window.ui:220 340 | msgid "Results" 341 | msgstr "結果" 342 | 343 | #: src/window.ui:140 src/window.ui:229 344 | msgid "Copy All" 345 | msgstr "すべてコピー" 346 | 347 | #: src/window.ui:151 src/window.ui:240 348 | msgid "Export" 349 | msgstr "エクスポート" 350 | 351 | #: src/window.ui:200 352 | msgid "No Results" 353 | msgstr "結果なし" 354 | 355 | #: src/window.ui:201 356 | msgid "Your calculation results will appear here" 357 | msgstr "計算結果がここに表示されます" -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext('netsleuth', preset: 'glib') -------------------------------------------------------------------------------- /po/netsleuth.pot: -------------------------------------------------------------------------------- 1 | # LANGUAGE translations for Netsleuth package. 2 | # Copyright (C) 2024-2025 Vladimir Kosolapov 3 | # This file is distributed under the same license as the Netsleuth package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: netsleuth\n" 10 | "Report-Msgid-Bugs-To: https://github.com/vmkspv/netsleuth/issues\n" 11 | "POT-Creation-Date: 2025-04-27 10:00+0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #. Translators: Netsleuth is the application name. Do NOT translate or transliterate this string! 21 | #: data/io.github.vmkspv.netsleuth.desktop.in:3 22 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:6 23 | msgid "Netsleuth" 24 | msgstr "" 25 | 26 | #: data/io.github.vmkspv.netsleuth.desktop.in:4 src/window.ui:33 27 | msgid "IP Subnet Calculator" 28 | msgstr "" 29 | 30 | #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! 31 | #: data/io.github.vmkspv.netsleuth.desktop.in:11 32 | msgid "Network;IP;Subnet;Mask;Host;Calculator;" 33 | msgstr "" 34 | 35 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:7 36 | msgid "Calculate IP subnets" 37 | msgstr "" 38 | 39 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:9 src/cmdline.py:33 40 | msgid "" 41 | "A simple utility for the calculation and analysis of IP subnet values, " 42 | "designed to simplify network configuration tasks." 43 | msgstr "" 44 | 45 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:14 46 | msgid "Calculation options" 47 | msgstr "" 48 | 49 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:18 50 | msgid "List of results" 51 | msgstr "" 52 | 53 | #: src/calculator.py:41 src/window.py:477 54 | msgid "Address" 55 | msgstr "" 56 | 57 | #: src/calculator.py:42 src/window.py:478 58 | msgid "Netmask" 59 | msgstr "" 60 | 61 | #: src/calculator.py:43 src/window.py:479 62 | msgid "Wildcard" 63 | msgstr "" 64 | 65 | #: src/calculator.py:44 src/window.py:480 66 | msgid "Network" 67 | msgstr "" 68 | 69 | #: src/calculator.py:45 src/window.py:481 70 | msgid "Broadcast" 71 | msgstr "" 72 | 73 | #: src/calculator.py:46 src/window.py:482 74 | msgid "First Host" 75 | msgstr "" 76 | 77 | #: src/calculator.py:47 src/window.py:483 78 | msgid "Last Host" 79 | msgstr "" 80 | 81 | #: src/calculator.py:48 src/window.py:138 src/window.py:484 82 | msgid "Total Hosts" 83 | msgstr "" 84 | 85 | #: src/calculator.py:49 src/window.py:485 86 | msgid "Category" 87 | msgstr "" 88 | 89 | #: src/calculator.py:50 src/window.py:486 90 | msgid "PTR Record" 91 | msgstr "" 92 | 93 | #: src/calculator.py:51 src/window.py:487 94 | msgid "IPv4 Mapped Address" 95 | msgstr "" 96 | 97 | #: src/calculator.py:52 src/window.py:488 98 | msgid "6to4 Prefix" 99 | msgstr "" 100 | 101 | #: src/calculator.py:56 src/cmdline.py:152 102 | msgid "Error" 103 | msgstr "" 104 | 105 | #: src/calculator.py:56 src/cmdline.py:152 106 | msgid "Invalid IP address or mask" 107 | msgstr "" 108 | 109 | #: src/calculator.py:79 src/window.py:489 110 | msgid "Private (Class A)" 111 | msgstr "" 112 | 113 | #: src/calculator.py:80 src/window.py:490 114 | msgid "Private (Class B)" 115 | msgstr "" 116 | 117 | #: src/calculator.py:81 src/window.py:491 118 | msgid "Private (Class C)" 119 | msgstr "" 120 | 121 | #: src/calculator.py:82 src/window.py:492 122 | msgid "Loopback" 123 | msgstr "" 124 | 125 | #: src/calculator.py:83 src/window.py:493 126 | msgid "Link-Local (APIPA)" 127 | msgstr "" 128 | 129 | #: src/calculator.py:84 src/window.py:494 130 | msgid "Multicast" 131 | msgstr "" 132 | 133 | #: src/calculator.py:85 src/window.py:495 134 | msgid "Reserved" 135 | msgstr "" 136 | 137 | #: src/calculator.py:88 src/window.py:496 138 | msgid "Public" 139 | msgstr "" 140 | 141 | #: src/cmdline.py:39 142 | msgid "ip for calculation" 143 | msgstr "" 144 | 145 | #: src/cmdline.py:45 146 | msgid "subnet mask (default: 24)" 147 | msgstr "" 148 | 149 | #: src/cmdline.py:50 150 | msgid "show binary values" 151 | msgstr "" 152 | 153 | #: src/cmdline.py:55 154 | msgid "show hexadecimal values" 155 | msgstr "" 156 | 157 | #: src/cmdline.py:61 158 | msgid "show this help message and exit" 159 | msgstr "" 160 | 161 | #: src/cmdline.py:66 162 | msgid "show version information and exit" 163 | msgstr "" 164 | 165 | #: src/cmdline.py:119 166 | #, python-brace-format 167 | msgid "" 168 | "netsleuth {version}\n" 169 | "Copyright (C) 2024-2025 Vladimir Kosolapov\n" 170 | "License GPLv3+: GNU GPL version 3 or later .\n" 172 | "This is free software: you are free to change and redistribute it.\n" 173 | "There is NO WARRANTY, to the extent permitted by law.\n" 174 | "\n" 175 | "Please report bugs to: ." 176 | msgstr "" 177 | 178 | #: src/cmdline.py:144 179 | msgid "options" 180 | msgstr "" 181 | 182 | #: src/cmdline.py:145 183 | msgid "general" 184 | msgstr "" 185 | 186 | #: src/cmdline.py:148 187 | msgid "positional arguments" 188 | msgstr "" 189 | 190 | #: src/cmdline.py:149 191 | msgid "usage:" 192 | msgstr "" 193 | 194 | #: src/window.py:91 195 | msgid "" 196 | "The 0.0.0.0 address is used to represent the default route or an unknown " 197 | "target network." 198 | msgstr "" 199 | 200 | #: src/window.py:92 201 | msgid "" 202 | "A PTR record enables reverse DNS lookup, translating an IP address back to a " 203 | "domain name." 204 | msgstr "" 205 | 206 | #: src/window.py:93 207 | msgid "" 208 | "The 240.0.0.0/4 block was originally reserved for future experiments, but is " 209 | "now considered legacy." 210 | msgstr "" 211 | 212 | #: src/window.py:94 213 | msgid "" 214 | "In a /30 subnet, there are only 2 usable IP addresses, often used for point-" 215 | "to-point links." 216 | msgstr "" 217 | 218 | #: src/window.py:95 219 | msgid "" 220 | "Wildcard mask inverts subnet masks, providing flexible IP filtering in " 221 | "access lists." 222 | msgstr "" 223 | 224 | #: src/window.py:96 225 | msgid "" 226 | "An IP's binary representation always has 32 bits, regardless of the decimal " 227 | "notation used." 228 | msgstr "" 229 | 230 | #: src/window.py:147 231 | msgid "Copy" 232 | msgstr "" 233 | 234 | #: src/window.py:193 src/window.py:407 235 | msgid "Copied to clipboard" 236 | msgstr "" 237 | 238 | #: src/window.py:245 src/window.ui:69 239 | msgid "History" 240 | msgstr "" 241 | 242 | #: src/window.py:256 243 | msgid "Clear" 244 | msgstr "" 245 | 246 | #: src/window.py:271 247 | msgid "No History" 248 | msgstr "" 249 | 250 | #: src/window.py:272 251 | msgid "Your calculation history will appear here" 252 | msgstr "" 253 | 254 | #: src/window.py:331 255 | msgid "Select" 256 | msgstr "" 257 | 258 | #: src/window.py:346 src/window.py:389 259 | #, python-brace-format 260 | msgid "Calculated: {ip}/{mask}" 261 | msgstr "" 262 | 263 | #: src/window.py:352 264 | msgid "History cleared" 265 | msgstr "" 266 | 267 | #: src/window.py:436 268 | msgid "Export results" 269 | msgstr "" 270 | 271 | #: src/window.py:511 272 | #, python-brace-format 273 | msgid "Saved to {file}" 274 | msgstr "" 275 | 276 | #: src/window.ui:40 277 | msgid "About Netsleuth" 278 | msgstr "" 279 | 280 | #: src/window.ui:62 281 | msgid "Details" 282 | msgstr "" 283 | 284 | #: src/window.ui:65 285 | msgid "IP Address" 286 | msgstr "" 287 | 288 | #: src/window.ui:81 289 | msgid "Subnet Mask" 290 | msgstr "" 291 | 292 | #: src/window.ui:88 293 | msgid "Show Binary" 294 | msgstr "" 295 | 296 | #: src/window.ui:89 297 | msgid "Display binary representation of IP addresses" 298 | msgstr "" 299 | 300 | #: src/window.ui:94 301 | msgid "Show Hexadecimal" 302 | msgstr "" 303 | 304 | #: src/window.ui:95 305 | msgid "Display hexadecimal representation of IP addresses" 306 | msgstr "" 307 | 308 | #: src/window.ui:107 309 | msgid "Calculate" 310 | msgstr "" 311 | 312 | #: src/window.ui:124 313 | msgid "Did you know?" 314 | msgstr "" 315 | 316 | #: src/window.ui:131 src/window.ui:182 src/window.ui:189 src/window.ui:220 317 | msgid "Results" 318 | msgstr "" 319 | 320 | #: src/window.ui:140 src/window.ui:229 321 | msgid "Copy All" 322 | msgstr "" 323 | 324 | #: src/window.ui:151 src/window.ui:240 325 | msgid "Export" 326 | msgstr "" 327 | 328 | #: src/window.ui:200 329 | msgid "No Results" 330 | msgstr "" 331 | 332 | #: src/window.ui:201 333 | msgid "Your calculation results will appear here" 334 | msgstr "" -------------------------------------------------------------------------------- /po/pt_BR.po: -------------------------------------------------------------------------------- 1 | # Brazilian Portuguese translations for Netsleuth package. 2 | # Copyright (C) 2024-2025 Vladimir Kosolapov 3 | # This file is distributed under the same license as the Netsleuth package. 4 | # Kelvin Ribeiro Novais, 2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: netsleuth\n" 9 | "Report-Msgid-Bugs-To: https://github.com/vmkspv/netsleuth/issues\n" 10 | "POT-Creation-Date: 2024-09-26 12:00+0000\n" 11 | "PO-Revision-Date: 2025-04-27 14:00+0300\n" 12 | "Last-Translator: Kelvin Ribeiro Novais\n" 13 | "Language-Team: Brazilian Portuguese\n" 14 | "Language: pt_BR\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 19 | "X-Generator: Gtranslator 47.0\n" 20 | 21 | #: data/io.github.vmkspv.netsleuth.desktop.in:4 src/window.ui:33 22 | msgid "IP Subnet Calculator" 23 | msgstr "Calculadora de sub-rede IP" 24 | 25 | #: data/io.github.vmkspv.netsleuth.desktop.in:11 26 | msgid "Network;IP;Subnet;Mask;Host;Calculator;" 27 | msgstr "Rede;IP;Sub-Rede;Máscara;Host;Calculadora;" 28 | 29 | #: src/calculator.py:41 src/window.py:477 30 | msgid "Address" 31 | msgstr "Endereço" 32 | 33 | #: src/calculator.py:42 src/window.py:478 34 | msgid "Netmask" 35 | msgstr "Máscara de rede" 36 | 37 | #: src/calculator.py:43 src/window.py:479 38 | msgid "Wildcard" 39 | msgstr "Wildcard" 40 | 41 | #: src/calculator.py:44 src/window.py:480 42 | msgid "Network" 43 | msgstr "Rede" 44 | 45 | #: src/calculator.py:46 src/window.py:482 46 | msgid "First Host" 47 | msgstr "Primeiro endereço" 48 | 49 | #: src/calculator.py:47 src/window.py:483 50 | msgid "Last Host" 51 | msgstr "Último endereço" 52 | 53 | #: src/calculator.py:45 src/window.py:481 54 | msgid "Broadcast" 55 | msgstr "Broadcast" 56 | 57 | #: src/calculator.py:48 src/window.py:138 src/window.py:484 58 | msgid "Total Hosts" 59 | msgstr "Total de endereços" 60 | 61 | #: src/calculator.py:49 src/window.py:485 62 | msgid "Category" 63 | msgstr "Categoria" 64 | 65 | #: src/calculator.py:50 src/window.py:486 66 | msgid "PTR Record" 67 | msgstr "Registro PTR" 68 | 69 | #: src/calculator.py:56 src/cmdline.py:152 70 | msgid "Error" 71 | msgstr "Erro" 72 | 73 | #: src/calculator.py:56 src/cmdline.py:152 74 | msgid "Invalid IP address or mask" 75 | msgstr "Endereço de IP ou máscara inválidos" 76 | 77 | #: src/calculator.py:79 src/window.py:489 78 | msgid "Private (Class A)" 79 | msgstr "Privado (Classe A)" 80 | 81 | #: src/calculator.py:80 src/window.py:490 82 | msgid "Private (Class B)" 83 | msgstr "Privado (Classe B)" 84 | 85 | #: src/calculator.py:81 src/window.py:491 86 | msgid "Private (Class C)" 87 | msgstr "Privado (Classe C)" 88 | 89 | #: src/calculator.py:82 src/window.py:492 90 | msgid "Loopback" 91 | msgstr "Loopback" 92 | 93 | #: src/calculator.py:83 src/window.py:493 94 | msgid "Link-Local (APIPA)" 95 | msgstr "Link-Local (APIPA)" 96 | 97 | #: src/calculator.py:84 src/window.py:494 98 | msgid "Multicast" 99 | msgstr "Multicast" 100 | 101 | #: src/calculator.py:85 src/window.py:495 102 | msgid "Reserved" 103 | msgstr "Reservado" 104 | 105 | #: src/calculator.py:88 src/window.py:496 106 | msgid "Public" 107 | msgstr "Público" 108 | 109 | #: src/window.py:147 110 | msgid "Copy" 111 | msgstr "Copiar" 112 | 113 | #: src/window.py:193 src/window.py:407 114 | msgid "Copied to clipboard" 115 | msgstr "Copiado para área de transferência" 116 | 117 | #: src/window.ui:40 118 | msgid "About Netsleuth" 119 | msgstr "Sobre o Netsleuth" 120 | 121 | #: src/window.ui:62 122 | msgid "Details" 123 | msgstr "Detalhes" 124 | 125 | #: src/window.ui:65 126 | msgid "IP Address" 127 | msgstr "Endereço de IP" 128 | 129 | #: src/window.ui:81 130 | msgid "Subnet Mask" 131 | msgstr "Máscara de Sub-rede" 132 | 133 | #: src/window.ui:88 134 | msgid "Show Binary" 135 | msgstr "Mostrar Binário" 136 | 137 | #: src/window.ui:89 138 | msgid "Display binary representation of IP addresses" 139 | msgstr "Mostrar representação binária dos endereços de IP" 140 | 141 | #: src/window.ui:107 142 | msgid "Calculate" 143 | msgstr "Calcular" 144 | 145 | #: src/window.ui:131 src/window.ui:182 src/window.ui:189 src/window.ui:220 146 | msgid "Results" 147 | msgstr "Resultados" -------------------------------------------------------------------------------- /po/ru.po: -------------------------------------------------------------------------------- 1 | # Russian translations for Netsleuth package. 2 | # Copyright (C) 2024-2025 Vladimir Kosolapov 3 | # This file is distributed under the same license as the Netsleuth package. 4 | # Vladimir Kosolapov, 2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: netsleuth\n" 9 | "Report-Msgid-Bugs-To: https://github.com/vmkspv/netsleuth/issues\n" 10 | "POT-Creation-Date: 2025-04-27 10:00+0300\n" 11 | "PO-Revision-Date: 2025-04-27 11:00+0300\n" 12 | "Last-Translator: Vladimir Kosolapov\n" 13 | "Language-Team: \n" 14 | "Language: ru\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " 19 | "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 20 | 21 | #. Translators: Netsleuth is the application name. Do NOT translate or transliterate this string! 22 | #: data/io.github.vmkspv.netsleuth.desktop.in:3 23 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:6 24 | msgid "Netsleuth" 25 | msgstr "Netsleuth" 26 | 27 | #: data/io.github.vmkspv.netsleuth.desktop.in:4 src/window.ui:33 28 | msgid "IP Subnet Calculator" 29 | msgstr "Калькулятор IP-подсетей" 30 | 31 | #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! 32 | #: data/io.github.vmkspv.netsleuth.desktop.in:11 33 | msgid "Network;IP;Subnet;Mask;Host;Calculator;" 34 | msgstr "Сеть;IP;Подсеть;Маска;Хост;Калькулятор;" 35 | 36 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:7 37 | msgid "Calculate IP subnets" 38 | msgstr "Рассчитывайте IP подсетей" 39 | 40 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:9 src/cmdline.py:33 41 | msgid "" 42 | "A simple utility for the calculation and analysis of IP subnet values, " 43 | "designed to simplify network configuration tasks." 44 | msgstr "" 45 | "Простая утилита для расчёта и анализа значений IP-подсетей, позволяющая " 46 | "упростить задачи конфигурирования сети." 47 | 48 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:14 49 | msgid "Calculation options" 50 | msgstr "Параметры расчёта" 51 | 52 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:18 53 | msgid "List of results" 54 | msgstr "Список результатов" 55 | 56 | #: src/calculator.py:41 src/window.py:477 57 | msgid "Address" 58 | msgstr "Адрес" 59 | 60 | #: src/calculator.py:42 src/window.py:478 61 | msgid "Netmask" 62 | msgstr "Маска" 63 | 64 | #: src/calculator.py:43 src/window.py:479 65 | msgid "Wildcard" 66 | msgstr "Обратная маска" 67 | 68 | #: src/calculator.py:44 src/window.py:480 69 | msgid "Network" 70 | msgstr "Адрес сети" 71 | 72 | #: src/calculator.py:45 src/window.py:481 73 | msgid "Broadcast" 74 | msgstr "Широковещательный адрес" 75 | 76 | #: src/calculator.py:46 src/window.py:482 77 | msgid "First Host" 78 | msgstr "Первый хост" 79 | 80 | #: src/calculator.py:47 src/window.py:483 81 | msgid "Last Host" 82 | msgstr "Последний хост" 83 | 84 | #: src/calculator.py:48 src/window.py:138 src/window.py:484 85 | msgid "Total Hosts" 86 | msgstr "Всего хостов" 87 | 88 | #: src/calculator.py:49 src/window.py:485 89 | msgid "Category" 90 | msgstr "Категория" 91 | 92 | #: src/calculator.py:50 src/window.py:486 93 | msgid "PTR Record" 94 | msgstr "Обратный адрес" 95 | 96 | #: src/calculator.py:51 src/window.py:487 97 | msgid "IPv4 Mapped Address" 98 | msgstr "Отображаемый IPv4-адрес" 99 | 100 | #: src/calculator.py:52 src/window.py:488 101 | msgid "6to4 Prefix" 102 | msgstr "6to4-префикс" 103 | 104 | #: src/calculator.py:56 src/cmdline.py:152 105 | msgid "Error" 106 | msgstr "Ошибка" 107 | 108 | #: src/calculator.py:56 src/cmdline.py:152 109 | msgid "Invalid IP address or mask" 110 | msgstr "Недопустимый IP-адрес или маска" 111 | 112 | #: src/calculator.py:79 src/window.py:489 113 | msgid "Private (Class A)" 114 | msgstr "Частный (класс A)" 115 | 116 | #: src/calculator.py:80 src/window.py:490 117 | msgid "Private (Class B)" 118 | msgstr "Частный (класс B)" 119 | 120 | #: src/calculator.py:81 src/window.py:491 121 | msgid "Private (Class C)" 122 | msgstr "Частный (класс C)" 123 | 124 | #: src/calculator.py:82 src/window.py:492 125 | msgid "Loopback" 126 | msgstr "Обратная связь (Loopback)" 127 | 128 | #: src/calculator.py:83 src/window.py:493 129 | msgid "Link-Local (APIPA)" 130 | msgstr "Локальная связь (APIPA)" 131 | 132 | #: src/calculator.py:84 src/window.py:494 133 | msgid "Multicast" 134 | msgstr "Мультикаст" 135 | 136 | #: src/calculator.py:85 src/window.py:495 137 | msgid "Reserved" 138 | msgstr "Зарезервированный" 139 | 140 | #: src/calculator.py:88 src/window.py:496 141 | msgid "Public" 142 | msgstr "Публичный" 143 | 144 | #: src/cmdline.py:39 145 | msgid "ip for calculation" 146 | msgstr "ip для выполнения расчёта" 147 | 148 | #: src/cmdline.py:45 149 | msgid "subnet mask (default: 24)" 150 | msgstr "маска подсети (по умолчанию: 24)" 151 | 152 | #: src/cmdline.py:50 153 | msgid "show binary values" 154 | msgstr "показать двоичные значения" 155 | 156 | #: src/cmdline.py:55 157 | msgid "show hexadecimal values" 158 | msgstr "показать шестнадцатеричные значения" 159 | 160 | #: src/cmdline.py:61 161 | msgid "show this help message and exit" 162 | msgstr "показать эту справку и выйти" 163 | 164 | #: src/cmdline.py:66 165 | msgid "show version information and exit" 166 | msgstr "показать информацию о версии и выйти" 167 | 168 | #: src/cmdline.py:119 169 | #, python-brace-format 170 | msgid "" 171 | "netsleuth {version}\n" 172 | "Copyright (C) 2024-2025 Vladimir Kosolapov\n" 173 | "License GPLv3+: GNU GPL version 3 or later .\n" 175 | "This is free software: you are free to change and redistribute it.\n" 176 | "There is NO WARRANTY, to the extent permitted by law.\n" 177 | "\n" 178 | "Please report bugs to: ." 179 | msgstr "" 180 | "netsleuth {version}\n" 181 | "Copyright (C) 2024-2025 Vladimir Kosolapov\n" 182 | "Лицензия GPLv3+: GNU GPL версии 3 или новее .\n" 184 | "Это свободное ПО: вы можете изменять и распространять его.\n" 185 | "Нет НИКАКИХ ГАРАНТИЙ в пределах действующего законодательства.\n" 186 | "\n" 187 | "Пожалуйста, сообщайте о проблемах на: ." 189 | 190 | #: src/cmdline.py:144 191 | msgid "options" 192 | msgstr "параметры" 193 | 194 | #: src/cmdline.py:145 195 | msgid "general" 196 | msgstr "общее" 197 | 198 | #: src/cmdline.py:148 199 | msgid "positional arguments" 200 | msgstr "позиционные аргументы" 201 | 202 | #: src/cmdline.py:149 203 | msgid "usage:" 204 | msgstr "использование:" 205 | 206 | #: src/window.py:91 207 | msgid "" 208 | "The 0.0.0.0 address is used to represent the default route or an unknown " 209 | "target network." 210 | msgstr "" 211 | "Адрес 0.0.0.0 используется для обозначения неизвестной целевой сети или " 212 | "маршрута по умолчанию." 213 | 214 | #: src/window.py:92 215 | msgid "" 216 | "A PTR record enables reverse DNS lookup, translating an IP address back to a " 217 | "domain name." 218 | msgstr "" 219 | "PTR-запись позволяет выполнять обратный поиск DNS, преобразуя IP-адрес в " 220 | "доменное имя." 221 | 222 | #: src/window.py:93 223 | msgid "" 224 | "The 240.0.0.0/4 block was originally reserved for future experiments, but is " 225 | "now considered legacy." 226 | msgstr "" 227 | "Изначально блок 240.0.0.0/4 был зарезервирован для будущих экспериментов, но " 228 | "теперь считается устаревшим." 229 | 230 | #: src/window.py:94 231 | msgid "" 232 | "In a /30 subnet, there are only 2 usable IP addresses, often used for point-" 233 | "to-point links." 234 | msgstr "" 235 | "В подсети /30 есть только 2 используемых IP-адреса, часто применяемых для " 236 | "соединений точка-точка." 237 | 238 | #: src/window.py:95 239 | msgid "" 240 | "Wildcard mask inverts subnet masks, providing flexible IP filtering in " 241 | "access lists." 242 | msgstr "" 243 | "Обратная маска инвертирует маски подсетей, обеспечивая гибкую фильтрацию IP в " 244 | "списках доступа." 245 | 246 | #: src/window.py:96 247 | msgid "" 248 | "An IP's binary representation always has 32 bits, regardless of the decimal " 249 | "notation used." 250 | msgstr "" 251 | "Двоичное представление IP-адреса всегда содержит 32 бита, независимо от " 252 | "используемой десятичной нотации." 253 | 254 | #: src/window.py:147 255 | msgid "Copy" 256 | msgstr "Копировать" 257 | 258 | #: src/window.py:193 src/window.py:407 259 | msgid "Copied to clipboard" 260 | msgstr "Скопировано в буфер обмена" 261 | 262 | #: src/window.py:245 src/window.ui:69 263 | msgid "History" 264 | msgstr "История" 265 | 266 | #: src/window.py:256 267 | msgid "Clear" 268 | msgstr "Очистить" 269 | 270 | #: src/window.py:271 271 | msgid "No History" 272 | msgstr "Ничего нет" 273 | 274 | #: src/window.py:272 275 | msgid "Your calculation history will appear here" 276 | msgstr "История ваших расчётов появится здесь" 277 | 278 | #: src/window.py:331 279 | msgid "Select" 280 | msgstr "Выбрать" 281 | 282 | #: src/window.py:346 src/window.py:389 283 | #, python-brace-format 284 | msgid "Calculated: {ip}/{mask}" 285 | msgstr "Рассчитано: {ip}/{mask}" 286 | 287 | #: src/window.py:352 288 | msgid "History cleared" 289 | msgstr "История очищена" 290 | 291 | #: src/window.py:436 292 | msgid "Export results" 293 | msgstr "Экспорт результатов" 294 | 295 | #: src/window.py:511 296 | #, python-brace-format 297 | msgid "Saved to {file}" 298 | msgstr "Сохранено в {file}" 299 | 300 | #: src/window.ui:40 301 | msgid "About Netsleuth" 302 | msgstr "О приложении" 303 | 304 | #: src/window.ui:62 305 | msgid "Details" 306 | msgstr "Детали" 307 | 308 | #: src/window.ui:65 309 | msgid "IP Address" 310 | msgstr "IP-адрес" 311 | 312 | #: src/window.ui:81 313 | msgid "Subnet Mask" 314 | msgstr "Маска подсети" 315 | 316 | #: src/window.ui:88 317 | msgid "Show Binary" 318 | msgstr "Двоичные значения" 319 | 320 | #: src/window.ui:89 321 | msgid "Display binary representation of IP addresses" 322 | msgstr "Отображать двоичное представление IP-адресов" 323 | 324 | #: src/window.ui:94 325 | msgid "Show Hexadecimal" 326 | msgstr "Шестнадцатеричные значения" 327 | 328 | #: src/window.ui:95 329 | msgid "Display hexadecimal representation of IP addresses" 330 | msgstr "Отображать шестнадцатеричное представление IP-адресов" 331 | 332 | #: src/window.ui:107 333 | msgid "Calculate" 334 | msgstr "Рассчитать" 335 | 336 | #: src/window.ui:124 337 | msgid "Did you know?" 338 | msgstr "Знаете ли вы?" 339 | 340 | #: src/window.ui:131 src/window.ui:182 src/window.ui:189 src/window.ui:220 341 | msgid "Results" 342 | msgstr "Результаты" 343 | 344 | #: src/window.ui:140 src/window.ui:229 345 | msgid "Copy All" 346 | msgstr "Копировать всё" 347 | 348 | #: src/window.ui:151 src/window.ui:240 349 | msgid "Export" 350 | msgstr "Экспортировать" 351 | 352 | #: src/window.ui:200 353 | msgid "No Results" 354 | msgstr "Ничего нет" 355 | 356 | #: src/window.ui:201 357 | msgid "Your calculation results will appear here" 358 | msgstr "Результаты ваших расчётов появятся здесь" -------------------------------------------------------------------------------- /po/uk.po: -------------------------------------------------------------------------------- 1 | # Ukrainian translations for Netsleuth package. 2 | # Copyright (C) 2024-2025 Vladimir Kosolapov 3 | # This file is distributed under the same license as the Netsleuth package. 4 | # Vladimir Kosolapov, 2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: netsleuth\n" 9 | "Report-Msgid-Bugs-To: https://github.com/vmkspv/netsleuth/issues\n" 10 | "POT-Creation-Date: 2025-04-27 10:00+0300\n" 11 | "PO-Revision-Date: 2025-04-27 11:00+0300\n" 12 | "Last-Translator: Vladimir Kosolapov\n" 13 | "Language-Team: \n" 14 | "Language: uk\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : " 19 | "n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" 20 | 21 | #. Translators: Netsleuth is the application name. Do NOT translate or transliterate this string! 22 | #: data/io.github.vmkspv.netsleuth.desktop.in:3 23 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:6 24 | msgid "Netsleuth" 25 | msgstr "Netsleuth" 26 | 27 | #: data/io.github.vmkspv.netsleuth.desktop.in:4 src/window.ui:33 28 | msgid "IP Subnet Calculator" 29 | msgstr "Калькулятор IP-підмереж" 30 | 31 | #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! 32 | #: data/io.github.vmkspv.netsleuth.desktop.in:11 33 | msgid "Network;IP;Subnet;Mask;Host;Calculator;" 34 | msgstr "Мережа;IP;Підмережа;Маска;Хост;Калькулятор;" 35 | 36 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:7 37 | msgid "Calculate IP subnets" 38 | msgstr "Розрахуйте IP підмереж" 39 | 40 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:9 src/cmdline.py:33 41 | msgid "" 42 | "A simple utility for the calculation and analysis of IP subnet values, " 43 | "designed to simplify network configuration tasks." 44 | msgstr "" 45 | "Проста утиліта для розрахунку та аналізу значень IP-підмереж, що дозволяє " 46 | "спростити завдання конфігурації мережі." 47 | 48 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:14 49 | msgid "Calculation options" 50 | msgstr "Параметри розрахунку" 51 | 52 | #: data/io.github.vmkspv.netsleuth.metainfo.xml.in:18 53 | msgid "List of results" 54 | msgstr "Список результатів" 55 | 56 | #: src/calculator.py:41 src/window.py:477 57 | msgid "Address" 58 | msgstr "Адреса" 59 | 60 | #: src/calculator.py:42 src/window.py:478 61 | msgid "Netmask" 62 | msgstr "Маска" 63 | 64 | #: src/calculator.py:43 src/window.py:479 65 | msgid "Wildcard" 66 | msgstr "Зворотня маска" 67 | 68 | #: src/calculator.py:44 src/window.py:480 69 | msgid "Network" 70 | msgstr "Адреса мережі" 71 | 72 | #: src/calculator.py:45 src/window.py:481 73 | msgid "Broadcast" 74 | msgstr "Широкомовна адреса" 75 | 76 | #: src/calculator.py:46 src/window.py:482 77 | msgid "First Host" 78 | msgstr "Перший хост" 79 | 80 | #: src/calculator.py:47 src/window.py:483 81 | msgid "Last Host" 82 | msgstr "Останній хост" 83 | 84 | #: src/calculator.py:48 src/window.py:138 src/window.py:484 85 | msgid "Total Hosts" 86 | msgstr "Усього хостів" 87 | 88 | #: src/calculator.py:49 src/window.py:485 89 | msgid "Category" 90 | msgstr "Категорія" 91 | 92 | #: src/calculator.py:50 src/window.py:486 93 | msgid "PTR Record" 94 | msgstr "Зворотня адреса" 95 | 96 | #: src/calculator.py:51 src/window.py:487 97 | msgid "IPv4 Mapped Address" 98 | msgstr "Відображувана IPv4-адреса" 99 | 100 | #: src/calculator.py:52 src/window.py:488 101 | msgid "6to4 Prefix" 102 | msgstr "6to4-префікс" 103 | 104 | #: src/calculator.py:56 src/cmdline.py:152 105 | msgid "Error" 106 | msgstr "Помилка" 107 | 108 | #: src/calculator.py:56 src/cmdline.py:152 109 | msgid "Invalid IP address or mask" 110 | msgstr "Недійсний IP-адреса або маска" 111 | 112 | #: src/calculator.py:79 src/window.py:489 113 | msgid "Private (Class A)" 114 | msgstr "Приватний (клас A)" 115 | 116 | #: src/calculator.py:80 src/window.py:490 117 | msgid "Private (Class B)" 118 | msgstr "Приватний (клас B)" 119 | 120 | #: src/calculator.py:81 src/window.py:491 121 | msgid "Private (Class C)" 122 | msgstr "Приватний (клас C)" 123 | 124 | #: src/calculator.py:82 src/window.py:492 125 | msgid "Loopback" 126 | msgstr "Зворотний зв'язок (Loopback)" 127 | 128 | #: src/calculator.py:83 src/window.py:493 129 | msgid "Link-Local (APIPA)" 130 | msgstr "Локальний зв'язок (APIPA)" 131 | 132 | #: src/calculator.py:84 src/window.py:494 133 | msgid "Multicast" 134 | msgstr "Мультикаст" 135 | 136 | #: src/calculator.py:85 src/window.py:495 137 | msgid "Reserved" 138 | msgstr "Зарезервований" 139 | 140 | #: src/calculator.py:88 src/window.py:496 141 | msgid "Public" 142 | msgstr "Публічний" 143 | 144 | #: src/cmdline.py:39 145 | msgid "ip for calculation" 146 | msgstr "ip для виконання розрахунку" 147 | 148 | #: src/cmdline.py:45 149 | msgid "subnet mask (default: 24)" 150 | msgstr "маска підмережі (за замовчуванням: 24)" 151 | 152 | #: src/cmdline.py:50 153 | msgid "show binary values" 154 | msgstr "вивести двійкові значення" 155 | 156 | #: src/cmdline.py:55 157 | msgid "show hexadecimal values" 158 | msgstr "вивести шістнадцяткові значення" 159 | 160 | #: src/cmdline.py:61 161 | msgid "show this help message and exit" 162 | msgstr "вивести цю довідку та вийти" 163 | 164 | #: src/cmdline.py:66 165 | msgid "show version information and exit" 166 | msgstr "вивести інформацію про версію та вийти" 167 | 168 | #: src/cmdline.py:119 169 | #, python-brace-format 170 | msgid "" 171 | "netsleuth {version}\n" 172 | "Copyright (C) 2024-2025 Vladimir Kosolapov\n" 173 | "License GPLv3+: GNU GPL version 3 or later .\n" 175 | "This is free software: you are free to change and redistribute it.\n" 176 | "There is NO WARRANTY, to the extent permitted by law.\n" 177 | "\n" 178 | "Please report bugs to: ." 179 | msgstr "" 180 | "netsleuth {version}\n" 181 | "Copyright (C) 2024-2025 Vladimir Kosolapov\n" 182 | "Ліцензія GPLv3+: GNU GPL версії 3 або новішій .\n" 184 | "Це вільне ПЗ: ви можете вільно змінювати і поширювати його.\n" 185 | "Немає ЖОДНИХ ГАРАНТІЙ у межах чинного законодавства.\n" 186 | "\n" 187 | "Будь ласка, повідомляйте про вади на: ." 189 | 190 | #: src/cmdline.py:144 191 | msgid "options" 192 | msgstr "параметри" 193 | 194 | #: src/cmdline.py:145 195 | msgid "general" 196 | msgstr "загальні" 197 | 198 | #: src/cmdline.py:148 199 | msgid "positional arguments" 200 | msgstr "позиційні аргументи" 201 | 202 | #: src/cmdline.py:149 203 | msgid "usage:" 204 | msgstr "використання:" 205 | 206 | #: src/window.py:91 207 | msgid "" 208 | "The 0.0.0.0 address is used to represent the default route or an unknown " 209 | "target network." 210 | msgstr "" 211 | "Адреса 0.0.0.0 використовується для позначення невідомої цільової мережі або " 212 | "маршруту за замовчуванням." 213 | 214 | #: src/window.py:92 215 | msgid "" 216 | "A PTR record enables reverse DNS lookup, translating an IP address back to a " 217 | "domain name." 218 | msgstr "" 219 | "PTR-запис дозволяє виконувати зворотний пошук DNS, перетворюючи IP-адресу на " 220 | "доменне ім'я." 221 | 222 | #: src/window.py:93 223 | msgid "" 224 | "The 240.0.0.0/4 block was originally reserved for future experiments, but is " 225 | "now considered legacy." 226 | msgstr "" 227 | "Спочатку блок 240.0.0.0/4 був зарезервований для майбутніх експериментів, але " 228 | "тепер вважається застарілим." 229 | 230 | #: src/window.py:94 231 | msgid "" 232 | "In a /30 subnet, there are only 2 usable IP addresses, often used for point-" 233 | "to-point links." 234 | msgstr "" 235 | "У підмережі /30 є лише 2 IP-адреси, що використовуються, часто застосовуються " 236 | "для з'єднань точка-точка." 237 | 238 | #: src/window.py:95 239 | msgid "" 240 | "Wildcard mask inverts subnet masks, providing flexible IP filtering in " 241 | "access lists." 242 | msgstr "" 243 | "Зворотня маска інвертує маски підмереж, забезпечуючи гнучку фільтрацію IP у " 244 | "списках доступу." 245 | 246 | #: src/window.py:96 247 | msgid "" 248 | "An IP's binary representation always has 32 bits, regardless of the decimal " 249 | "notation used." 250 | msgstr "" 251 | "Двійкове представлення IP-адреси завжди містить 32 біти, незалежно від " 252 | "використаної десяткової нотації." 253 | 254 | #: src/window.py:147 255 | msgid "Copy" 256 | msgstr "Копіювати" 257 | 258 | #: src/window.py:193 src/window.py:407 259 | msgid "Copied to clipboard" 260 | msgstr "Скопійовано до буфера обміну" 261 | 262 | #: src/window.py:245 src/window.ui:69 263 | msgid "History" 264 | msgstr "Історія" 265 | 266 | #: src/window.py:256 267 | msgid "Clear" 268 | msgstr "Очистити" 269 | 270 | #: src/window.py:271 271 | msgid "No History" 272 | msgstr "Нічого немає" 273 | 274 | #: src/window.py:272 275 | msgid "Your calculation history will appear here" 276 | msgstr "Історія ваших розрахунків з'явиться тут" 277 | 278 | #: src/window.py:331 279 | msgid "Select" 280 | msgstr "Вибрати" 281 | 282 | #: src/window.py:346 src/window.py:389 283 | #, python-brace-format 284 | msgid "Calculated: {ip}/{mask}" 285 | msgstr "Розраховано: {ip}/{mask}" 286 | 287 | #: src/window.py:352 288 | msgid "History cleared" 289 | msgstr "Історія очищена" 290 | 291 | #: src/window.py:436 292 | msgid "Export results" 293 | msgstr "Експорт результатів" 294 | 295 | #: src/window.py:511 296 | #, python-brace-format 297 | msgid "Saved to {file}" 298 | msgstr "Збережено у {file}" 299 | 300 | #: src/window.ui:40 301 | msgid "About Netsleuth" 302 | msgstr "Про Netsleuth" 303 | 304 | #: src/window.ui:62 305 | msgid "Details" 306 | msgstr "Деталі" 307 | 308 | #: src/window.ui:65 309 | msgid "IP Address" 310 | msgstr "IP-адреса" 311 | 312 | #: src/window.ui:81 313 | msgid "Subnet Mask" 314 | msgstr "Маска підмережі" 315 | 316 | #: src/window.ui:88 317 | msgid "Show Binary" 318 | msgstr "Двійкові значення" 319 | 320 | #: src/window.ui:89 321 | msgid "Display binary representation of IP addresses" 322 | msgstr "Відображати двійкове представлення IP-адресiв" 323 | 324 | #: src/window.ui:94 325 | msgid "Show Hexadecimal" 326 | msgstr "Шістнадцяткові значення" 327 | 328 | #: src/window.ui:95 329 | msgid "Display hexadecimal representation of IP addresses" 330 | msgstr "Відображати шістнадцяткове представлення IP-адресiв" 331 | 332 | #: src/window.ui:107 333 | msgid "Calculate" 334 | msgstr "Розрахувати" 335 | 336 | #: src/window.ui:124 337 | msgid "Did you know?" 338 | msgstr "Чи знаєте ви?" 339 | 340 | #: src/window.ui:131 src/window.ui:182 src/window.ui:189 src/window.ui:220 341 | msgid "Results" 342 | msgstr "Результати" 343 | 344 | #: src/window.ui:140 src/window.ui:229 345 | msgid "Copy All" 346 | msgstr "Копіювати все" 347 | 348 | #: src/window.ui:151 src/window.ui:240 349 | msgid "Export" 350 | msgstr "Експортувати" 351 | 352 | #: src/window.ui:200 353 | msgid "No Results" 354 | msgstr "Нічого немає" 355 | 356 | #: src/window.ui:201 357 | msgid "Your calculation results will appear here" 358 | msgstr "Результати ваших розрахунків з'являться тут" -------------------------------------------------------------------------------- /po/update-pot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GREEN='\033[0;32m' 4 | BLUE='\033[0;94m' 5 | GRAY='\033[0;37m' 6 | NC='\033[0m' 7 | 8 | print_step() { echo -e "${BLUE}[*]${NC} $1"; } 9 | print_success() { echo -e "${GREEN}[✓]${NC} $1"; } 10 | print_info() { echo -e "${GRAY}[i]${NC} $1"; } 11 | 12 | cd "$(dirname "$0")/.." 13 | 14 | print_step "Updating POTFILES..." 15 | { 16 | find data -type f \( -name "*.desktop.in" -o -name "*.metainfo.xml.in" \) ! -name "*.SearchProvider.desktop.in" 17 | find src -type f -name "*.py" -print0 | xargs -0 grep -l -E "(_\(['\"]|N_\(['\"]|C_\(['\"])" 18 | find src -type f -name "*.ui" 19 | } | sort > po/POTFILES 20 | print_success "POTFILES updated successfully." 21 | 22 | print_step "Extracting translatable strings..." 23 | xgettext --files-from=po/POTFILES \ 24 | --from-code=UTF-8 \ 25 | --output=po/netsleuth.pot \ 26 | --package-name=netsleuth \ 27 | --copyright-holder="Vladimir Kosolapov" \ 28 | --msgid-bugs-address="https://github.com/vmkspv/netsleuth/issues" \ 29 | --add-comments=Translators \ 30 | --keyword=_ \ 31 | --keyword=C_:1c,2 \ 32 | --keyword=N_ \ 33 | --keyword=Q_ \ 34 | 2>/dev/null 35 | 36 | print_step "Updating POT creation date..." 37 | sed -i 's/^"POT-Creation-Date:.*/"POT-Creation-Date: '"$(date +'%Y-%m-%d %H:%M%z')"'\\n"/' po/netsleuth.pot 38 | 39 | print_success "netsleuth.pot updated successfully." 40 | print_info "Total translatable strings: $(grep -c msgid po/netsleuth.pot)" 41 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmkspv/netsleuth/2943c713088b6903207e09aca1d39b71cc3541cd/src/__init__.py -------------------------------------------------------------------------------- /src/calculator.py: -------------------------------------------------------------------------------- 1 | # calculator.py 2 | # 3 | # Copyright 2024 Vladimir Kosolapov 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | from ipaddress import IPv4Interface, IPv4Address 21 | 22 | class IPCalculator: 23 | def __init__(self): 24 | self.show_binary = False 25 | self.show_hex = False 26 | self.binary_cache = {} 27 | self.hex_cache = {} 28 | 29 | def set_show_binary(self, show_binary): 30 | self.show_binary = show_binary 31 | 32 | def set_show_hex(self, show_hex): 33 | self.show_hex = show_hex 34 | 35 | def calculate(self, ip, mask): 36 | try: 37 | interface = IPv4Interface(f"{ip}/{mask}") 38 | network = interface.network 39 | host_count = network.num_addresses - (2 if mask < 31 else 0) 40 | results = { 41 | _('Address'): self.format_ip(interface.ip), 42 | _('Netmask'): self.format_ip(network.netmask), 43 | _('Wildcard'): self.format_ip(network.hostmask), 44 | _('Network'): self.format_ip(network.network_address), 45 | _('Broadcast'): self.format_ip(network.broadcast_address) if mask < 31 else None, 46 | _('First Host'): self.format_ip(network.network_address + (0 if mask >= 31 else 1)), 47 | _('Last Host'): self.format_ip(network.broadcast_address - (0 if mask >= 31 else 1)), 48 | _('Total Hosts'): f"{host_count}{self.get_host_count_math(mask)}", 49 | _('Category'): self.get_ip_class(interface.ip), 50 | _('PTR Record'): self.get_ptr_record(interface.ip), 51 | _('IPv4 Mapped Address'): self.get_ipv4_mapped(interface.ip), 52 | _('6to4 Prefix'): self.get_6to4_prefix(interface.ip) 53 | } 54 | return results 55 | except ValueError: 56 | return {_('Error'): _('Invalid IP address or mask')} 57 | 58 | def format_ip(self, ip): 59 | result = str(ip) 60 | if self.show_binary: 61 | if ip not in self.binary_cache: 62 | self.binary_cache[ip] = self.ip_to_binary(ip) 63 | result += f"\n{self.binary_cache[ip]}" 64 | if self.show_hex: 65 | if ip not in self.hex_cache: 66 | self.hex_cache[ip] = self.ip_to_hex(ip) 67 | result += f"\n{self.hex_cache[ip]}" 68 | return result 69 | 70 | def ip_to_binary(self, ip): 71 | return '.'.join(f"{int(octet):08b}" for octet in str(ip).split('.')) 72 | 73 | def ip_to_hex(self, ip): 74 | return '.'.join(f"{int(octet):02X}" for octet in str(ip).split('.')) 75 | 76 | def get_ip_class(self, ip): 77 | ip_int = int(IPv4Address(ip)) 78 | ip_ranges = ( 79 | (167772160, 184549375, _('Private (Class A)')), 80 | (2886729728, 2887778303, _('Private (Class B)')), 81 | (3232235520, 3232301055, _('Private (Class C)')), 82 | (2130706432, 2147483647, _('Loopback')), 83 | (2851995648, 2852061183, _('Link-Local (APIPA)')), 84 | (3758096384, 4026531839, _('Multicast')), 85 | (4026531840, 4294967295, _('Reserved')) 86 | ) 87 | 88 | return next((category for start, end, category in ip_ranges if start <= ip_int <= end), _('Public')) 89 | 90 | def int_to_dotted_netmask(self, mask_int): 91 | mask = (0xffffffff << (32 - mask_int)) & 0xffffffff 92 | return f"{mask>>24 & 255}.{mask>>16 & 255}.{mask>>8 & 255}.{mask & 255}" 93 | 94 | def get_host_count_math(self, mask): 95 | if mask >= 31: 96 | return "" 97 | exponent = 32 - mask 98 | superscript = ''.join('⁰¹²³⁴⁵⁶⁷⁸⁹'[int(d)] for d in str(exponent)) 99 | return f" (2{superscript} - 2)" 100 | 101 | def get_ptr_record(self, ip): 102 | return f"{'.'.join(str(ip).split('.')[::-1])}.in-addr.arpa" 103 | 104 | def get_ipv4_mapped(self, ip): 105 | hex_parts = [f"{int(octet):02x}" for octet in str(ip).split('.')] 106 | return f"::ffff:{hex_parts[0]}{hex_parts[1]}.{hex_parts[2]}{hex_parts[3]}" 107 | 108 | def get_6to4_prefix(self, ip): 109 | hex_parts = [f"{int(octet):02x}" for octet in str(ip).split('.')] 110 | return f"2002:{hex_parts[0]}{hex_parts[1]}.{hex_parts[2]}{hex_parts[3]}::/48" 111 | -------------------------------------------------------------------------------- /src/cmdline.py: -------------------------------------------------------------------------------- 1 | # cmdline.py 2 | # 3 | # Copyright 2024 Vladimir Kosolapov 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | import sys 21 | import argparse 22 | from ipaddress import AddressValueError, IPv4Address 23 | from .calculator import IPCalculator 24 | 25 | class CommandLineInterface: 26 | def __init__(self, version): 27 | self.version = version 28 | self.parser = self.create_argument_parser() 29 | self.calculator = IPCalculator() 30 | 31 | def create_argument_parser(self): 32 | parser = TranslatedArgumentParser( 33 | description=_('A simple utility for the calculation and analysis of IP subnet values, designed to simplify network configuration tasks.') 34 | ) 35 | parser.add_argument( 36 | 'ip_address', 37 | type=self.validate_ip, 38 | nargs='?', 39 | help=_('ip for calculation') 40 | ) 41 | parser.options_group.add_argument( 42 | '-m', '--mask', 43 | type=self.validate_mask, 44 | default=24, 45 | help=_('subnet mask (default: 24)') 46 | ) 47 | parser.options_group.add_argument( 48 | '--binary', 49 | action='store_true', 50 | help=_('show binary values') 51 | ) 52 | parser.options_group.add_argument( 53 | '--hex', 54 | action='store_true', 55 | help=_('show hexadecimal values') 56 | ) 57 | parser.general_group.add_argument( 58 | '-h', '--help', 59 | action='help', 60 | default=argparse.SUPPRESS, 61 | help=_('show this help message and exit') 62 | ) 63 | parser.general_group.add_argument( 64 | '-v', '--version', 65 | action='store_true', 66 | help=_('show version information and exit') 67 | ) 68 | return parser 69 | 70 | def validate_ip(self, value): 71 | try: 72 | IPv4Address(value) 73 | return value 74 | except (AddressValueError, ValueError): 75 | raise argparse.ArgumentTypeError('') 76 | 77 | def validate_mask(self, value): 78 | try: 79 | mask = int(value) 80 | if 0 <= mask <= 32: 81 | return mask 82 | except ValueError: 83 | try: 84 | addr = IPv4Address(value) 85 | binary_str = bin(int(addr))[2:].zfill(32) 86 | if '01' not in binary_str: 87 | return binary_str.count('1') 88 | except (AddressValueError, ValueError): 89 | pass 90 | raise argparse.ArgumentTypeError('') 91 | 92 | def format_output(self, results): 93 | if not results: 94 | return '' 95 | 96 | max_key_length = max(len(key) for key in results.keys()) + 1 97 | output = [] 98 | 99 | for key, value in results.items(): 100 | if value is None: 101 | continue 102 | 103 | if isinstance(value, str): 104 | parts = value.split(' ', 1) 105 | if len(parts) > 1 and parts[1].startswith('(2'): 106 | value = parts[0] 107 | 108 | if '' in str(value): 109 | parts = value.split('') 110 | output.append(f"{key:{max_key_length}}: {parts[0].strip()}") 111 | for part in parts[1:]: 112 | output.append(f"{'':{max_key_length}} {part.replace('', '').strip()}") 113 | else: 114 | output.append(f"{key:{max_key_length}}: {value}") 115 | 116 | return '\n'.join(output) 117 | 118 | def print_version(self): 119 | print(_('''netsleuth {version} 120 | Copyright (C) 2024-2025 Vladimir Kosolapov 121 | License GPLv3+: GNU GPL version 3 or later . 122 | This is free software: you are free to change and redistribute it. 123 | There is NO WARRANTY, to the extent permitted by law. 124 | 125 | Please report bugs to: .''').format(version=self.version)) 126 | sys.exit(0) 127 | 128 | def run(self): 129 | args = self.parser.parse_args() 130 | 131 | if args.version: 132 | self.print_version() 133 | 134 | self.calculator.set_show_binary(args.binary) 135 | self.calculator.set_show_hex(args.hex) 136 | results = self.calculator.calculate(args.ip_address, args.mask) 137 | print(self.format_output(results)) 138 | 139 | class TranslatedArgumentParser(argparse.ArgumentParser): 140 | def __init__(self, *args, **kwargs): 141 | kwargs['add_help'] = False 142 | kwargs['formatter_class'] = lambda prog: argparse.HelpFormatter(prog, max_help_position=32) 143 | super().__init__(*args, **kwargs) 144 | self.options_group = self.add_argument_group(_('options')) 145 | self.general_group = self.add_argument_group(_('general')) 146 | 147 | def format_help(self): 148 | self._positionals.title = _('positional arguments') 149 | return super().format_help().replace('usage:', _('usage:')) 150 | 151 | def error(self, message): 152 | print(f"{_('Error')}: {_('Invalid IP address or mask')}", file=sys.stderr) 153 | sys.exit(1) 154 | 155 | def main(version): 156 | cli = CommandLineInterface(version) 157 | cli.run() 158 | 159 | if __name__ == '__main__': 160 | main() 161 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | # main.py 2 | # 3 | # Copyright 2024 Vladimir Kosolapov 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | import sys 21 | import locale 22 | import gi 23 | 24 | gi.require_version('Gtk', '4.0') 25 | gi.require_version('Adw', '1') 26 | 27 | from gi.repository import Adw, Gtk, Gio 28 | from .window import NetsleuthWindow 29 | from .cmdline import main as cli_main 30 | 31 | translators = { 32 | 'bg': 'twlvnn kraftwerk https://github.com/twlvnn', 33 | 'it': 'Albano Battistella https://github.com/albanobattistella', 34 | 'ja': 'Ryo Nakano https://github.com/ryonakano', 35 | 'pt_BR': 'Kelvin Ribeiro Novais https://github.com/KelvinNovais', 36 | 'ru': 'Vladimir Kosolapov https://github.com/vmkspv', 37 | 'uk': 'Vladimir Kosolapov https://github.com/vmkspv' 38 | } 39 | 40 | class NetsleuthApplication(Adw.Application): 41 | def __init__(self, version): 42 | super().__init__(application_id='io.github.vmkspv.netsleuth', 43 | flags=Gio.ApplicationFlags.DEFAULT_FLAGS) 44 | self.create_action("quit", lambda *_: self.quit(), ['q']) 45 | self.create_action("close-window", self.on_close_window_action, ['w']) 46 | self.create_action("new-window", self.on_new_window_action, ['n']) 47 | self.create_action("about", self.on_about_action) 48 | self.version = version 49 | 50 | def do_activate(self): 51 | self.new_window() 52 | 53 | def new_window(self): 54 | win = NetsleuthWindow(application=self) 55 | win.present() 56 | 57 | def on_new_window_action(self, *args): 58 | self.new_window() 59 | 60 | def get_translator_credits(self): 61 | locale_code = locale.getlocale()[0] or '' 62 | return translators.get(locale_code) or translators.get(locale_code[:2], '') 63 | 64 | def on_about_action(self, widget, _): 65 | about = Adw.AboutDialog.new_from_appdata('io/github/vmkspv/netsleuth/metainfo.xml', self.version) 66 | about.set_developers(['Vladimir Kosolapov https://github.com/vmkspv']) 67 | about.set_artists(['Vladimir Kosolapov https://github.com/vmkspv']) 68 | about.set_translator_credits(self.get_translator_credits()) 69 | about.set_copyright('© 2024-2025 Vladimir Kosolapov') 70 | about.present(self.props.active_window) 71 | 72 | def on_close_window_action(self, *args): 73 | self.props.active_window.close() 74 | 75 | def create_action(self, name, callback, shortcuts=None): 76 | action = Gio.SimpleAction.new(name, None) 77 | action.connect("activate", callback) 78 | self.add_action(action) 79 | if shortcuts: 80 | self.set_accels_for_action(f"app.{name}", shortcuts) 81 | 82 | def main(version): 83 | if len(sys.argv) == 1: 84 | app = NetsleuthApplication(version) 85 | return app.run(sys.argv) 86 | return cli_main(version) 87 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | pkgdatadir = get_option('prefix') / get_option('datadir') / meson.project_name() 2 | moduledir = pkgdatadir / 'netsleuth' 3 | gnome = import('gnome') 4 | 5 | gnome.compile_resources('netsleuth', 6 | 'netsleuth.gresource.xml', 7 | gresource_bundle: true, 8 | install: true, 9 | install_dir: pkgdatadir 10 | ) 11 | 12 | python = import('python') 13 | 14 | conf = configuration_data() 15 | conf.set('PYTHON', python.find_installation('python3').full_path()) 16 | conf.set('VERSION', meson.project_version()) 17 | conf.set('localedir', get_option('prefix') / get_option('localedir')) 18 | conf.set('pkgdatadir', pkgdatadir) 19 | 20 | configure_file( 21 | input: 'netsleuth.in', 22 | output: 'netsleuth', 23 | configuration: conf, 24 | install: true, 25 | install_dir: get_option('bindir'), 26 | install_mode: 'r-xr-xr-x' 27 | ) 28 | 29 | configure_file( 30 | input: 'netsleuth_search_provider.in', 31 | output: 'netsleuth_search_provider', 32 | configuration: conf, 33 | install: true, 34 | install_dir: get_option('bindir'), 35 | install_mode: 'r-xr-xr-x' 36 | ) 37 | 38 | netsleuth_sources = [ 39 | '__init__.py', 40 | 'main.py', 41 | 'window.py', 42 | 'calculator.py', 43 | 'cmdline.py' 44 | ] 45 | 46 | install_data(netsleuth_sources, install_dir: moduledir) -------------------------------------------------------------------------------- /src/netsleuth.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | ../data/io.github.vmkspv.netsleuth.metainfo.xml.in 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/netsleuth.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | 3 | # netsleuth.in 4 | # 5 | # Copyright 2024 Vladimir Kosolapov 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | # SPDX-License-Identifier: GPL-3.0-or-later 21 | 22 | import os 23 | import sys 24 | import signal 25 | import locale 26 | import gettext 27 | 28 | VERSION = '@VERSION@' 29 | pkgdatadir = '@pkgdatadir@' 30 | localedir = '@localedir@' 31 | 32 | sys.path.insert(1, pkgdatadir) 33 | signal.signal(signal.SIGINT, signal.SIG_DFL) 34 | locale.bindtextdomain('netsleuth', localedir) 35 | locale.textdomain('netsleuth') 36 | gettext.install('netsleuth', localedir) 37 | 38 | if __name__ == '__main__': 39 | import gi 40 | 41 | from gi.repository import Gio 42 | resource = Gio.Resource.load(os.path.join(pkgdatadir, 'netsleuth.gresource')) 43 | resource._register() 44 | 45 | from netsleuth import main 46 | sys.exit(main.main(VERSION)) -------------------------------------------------------------------------------- /src/netsleuth_search_provider.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | 3 | # netsleuth_search_provider.in 4 | # 5 | # Copyright 2024 Vladimir Kosolapov 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | # SPDX-License-Identifier: GPL-3.0-or-later 21 | 22 | import os 23 | import sys 24 | import gi 25 | from json import load, dump 26 | from gi.repository import GObject 27 | 28 | gi.require_version('Gtk', '4.0') 29 | from gi.repository import Gio, GLib 30 | 31 | SEARCH_BUS_NAME = 'io.github.vmkspv.netsleuth.SearchProvider' 32 | SEARCH_PATH = '/io/github/vmkspv/netsleuth/SearchProvider' 33 | SEARCH_INTERFACE = ''' 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ''' 61 | 62 | class SearchProvider(GObject.Object): 63 | def __init__(self): 64 | super().__init__() 65 | self.history_file = os.path.join(GLib.get_user_config_dir(), 'netsleuth', 'history.json') 66 | self.timeout_id = None 67 | self.is_registered = False 68 | self.connection = None 69 | self._file_handles = set() 70 | 71 | Gio.bus_own_name( 72 | Gio.BusType.SESSION, 73 | SEARCH_BUS_NAME, 74 | Gio.BusNameOwnerFlags.NONE, 75 | self.on_bus_acquired, 76 | None, 77 | self.on_name_lost 78 | ) 79 | 80 | def cleanup(self): 81 | if self.timeout_id: 82 | try: 83 | GLib.source_remove(self.timeout_id) 84 | except GLib.Error as e: 85 | print(f"Error removing timeout: {e}") 86 | self.timeout_id = None 87 | 88 | if self.connection: 89 | try: 90 | self.connection.unregister_object(SEARCH_PATH) 91 | except GLib.Error as e: 92 | print(f"Error unregistering object: {e}") 93 | self.connection = None 94 | 95 | for handle in self._file_handles.copy(): 96 | try: 97 | handle.close() 98 | except: 99 | pass 100 | self._file_handles.clear() 101 | 102 | self.is_registered = False 103 | 104 | def reset_timeout(self): 105 | if self.timeout_id: 106 | GLib.source_remove(self.timeout_id) 107 | self.timeout_id = GLib.timeout_add_seconds(10, self.on_timeout) 108 | 109 | def on_timeout(self): 110 | self.timeout_id = None 111 | if loop.is_running(): 112 | loop.quit() 113 | return GLib.SOURCE_REMOVE 114 | 115 | def on_bus_acquired(self, connection, name): 116 | self.connection = connection 117 | info = Gio.DBusNodeInfo.new_for_xml(SEARCH_INTERFACE) 118 | 119 | self.connection.register_object( 120 | SEARCH_PATH, 121 | info.interfaces[0], 122 | self.handle_method_call, 123 | None, 124 | None 125 | ) 126 | self.is_registered = True 127 | self.reset_timeout() 128 | 129 | def handle_method_call(self, connection, sender, object_path, interface_name, method_name, parameters, invocation): 130 | if not self.is_registered: 131 | self.is_registered = True 132 | 133 | self.reset_timeout() 134 | 135 | try: 136 | args = parameters.unpack() 137 | method = getattr(self, method_name) 138 | result = method(*args) 139 | 140 | if method_name in ['GetInitialResultSet', 'GetSubsearchResultSet']: 141 | invocation.return_value(GLib.Variant('(as)', (result,))) 142 | elif method_name == 'GetResultMetas': 143 | invocation.return_value(GLib.Variant('(aa{sv})', (result,))) 144 | else: 145 | invocation.return_value(None) 146 | except Exception as e: 147 | invocation.return_error_literal( 148 | Gio.dbus_error_quark(), 149 | Gio.DBusError.FAILED, 150 | str(e) 151 | ) 152 | 153 | def load_history(self): 154 | try: 155 | if os.path.exists(self.history_file): 156 | with open(self.history_file, 'r') as f: 157 | self._file_handles.add(f) 158 | data = load(f) 159 | self._file_handles.remove(f) 160 | return data 161 | except (IOError, ValueError) as e: 162 | print(f"Error loading history: {e}") 163 | return [] 164 | finally: 165 | if f in self._file_handles: 166 | self._file_handles.remove(f) 167 | return [] 168 | 169 | def save_history(self, history): 170 | try: 171 | os.makedirs(os.path.dirname(self.history_file), exist_ok=True) 172 | with open(self.history_file, 'w') as f: 173 | dump(history, f, indent=4) 174 | except (IOError, ValueError) as e: 175 | print(f"Error saving history: {e}") 176 | 177 | def GetInitialResultSet(self, terms): 178 | history = self.load_history() 179 | search_text = ' '.join(terms).lower() 180 | 181 | return [f"{item['ip']}/{item['mask']}" 182 | for item in history 183 | if search_text in f"{item['ip']}/{item['mask']}".lower()] 184 | 185 | def GetSubsearchResultSet(self, previous_results, terms): 186 | return self.GetInitialResultSet(terms) 187 | 188 | def int_to_dotted_netmask(self, mask): 189 | bits = '1' * mask + '0' * (32 - mask) 190 | octets = [bits[i:i+8] for i in range(0, 32, 8)] 191 | return '.'.join(str(int(octet, 2)) for octet in octets) 192 | 193 | def GetResultMetas(self, identifiers): 194 | metas = [] 195 | for identifier in identifiers: 196 | ip, mask = identifier.split('/') 197 | mask = int(mask) 198 | meta = { 199 | 'id': GLib.Variant('s', identifier), 200 | 'name': GLib.Variant('s', identifier), 201 | 'description': GLib.Variant('s', self.int_to_dotted_netmask(mask)), 202 | 'gicon': GLib.Variant('s', 'io.github.vmkspv.netsleuth') 203 | } 204 | metas.append(meta) 205 | return metas 206 | 207 | def ActivateResult(self, identifier, terms, timestamp): 208 | try: 209 | history = self.load_history() 210 | ip, mask = identifier.split('/') 211 | mask = int(mask) 212 | 213 | for item in history: 214 | if item['ip'] == ip and item['mask'] == mask: 215 | item['selected'] = True 216 | break 217 | 218 | os.makedirs(os.path.dirname(self.history_file), exist_ok=True) 219 | with open(self.history_file, 'w') as f: 220 | dump(history, f, indent=4) 221 | 222 | context = GLib.MainContext.default() 223 | app_info = Gio.DesktopAppInfo.new('io.github.vmkspv.netsleuth.desktop') 224 | if app_info: 225 | context.iteration(False) 226 | app_info.launch([], None) 227 | GLib.timeout_add(100, lambda: loop.quit()) 228 | 229 | except Exception as e: 230 | print(f"Error in ActivateResult: {str(e)}") 231 | 232 | def LaunchSearch(self, terms, timestamp): 233 | launcher = Gio.DesktopAppInfo.new('io.github.vmkspv.netsleuth.desktop') 234 | launcher.launch([], None) 235 | 236 | def on_name_lost(self, connection, name): 237 | self.cleanup() 238 | if loop.is_running(): 239 | loop.quit() 240 | 241 | if __name__ == '__main__': 242 | provider = SearchProvider() 243 | loop = GLib.MainLoop() 244 | 245 | try: 246 | loop.run() 247 | except KeyboardInterrupt: 248 | loop.quit() -------------------------------------------------------------------------------- /src/window.py: -------------------------------------------------------------------------------- 1 | # window.py 2 | # 3 | # Copyright 2024 Vladimir Kosolapov 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | from os import path, makedirs 21 | from json import dump, load 22 | from random import choice 23 | 24 | from gi.repository import Adw, Gtk, Gdk, GLib 25 | from .calculator import IPCalculator 26 | 27 | @Gtk.Template(resource_path='/io/github/vmkspv/netsleuth/window.ui') 28 | class NetsleuthWindow(Adw.ApplicationWindow): 29 | __gtype_name__ = 'NetsleuthWindow' 30 | 31 | ip_entry = Gtk.Template.Child() 32 | history_button = Gtk.Template.Child() 33 | mask_dropdown = Gtk.Template.Child() 34 | show_binary_switch = Gtk.Template.Child() 35 | show_hex_switch = Gtk.Template.Child() 36 | calculate_button = Gtk.Template.Child() 37 | fact_of_the_day_box = Gtk.Template.Child() 38 | fact_row = Gtk.Template.Child() 39 | results_group_main = Gtk.Template.Child() 40 | copy_all_button_main = Gtk.Template.Child() 41 | export_button_main = Gtk.Template.Child() 42 | results_box_main = Gtk.Template.Child() 43 | results_group = Gtk.Template.Child() 44 | copy_all_button = Gtk.Template.Child() 45 | export_button = Gtk.Template.Child() 46 | results_box = Gtk.Template.Child() 47 | results_stack = Gtk.Template.Child() 48 | empty_results = Gtk.Template.Child() 49 | main_content = Gtk.Template.Child() 50 | split_view = Gtk.Template.Child() 51 | toast_overlay = Gtk.Template.Child() 52 | 53 | def __init__(self, **kwargs): 54 | super().__init__(**kwargs) 55 | self.set_title('Netsleuth') 56 | self.calculator = IPCalculator() 57 | self.results = {} 58 | self.history_dialog = None 59 | self.setup_mask_dropdown() 60 | self.connect_signals() 61 | self.calculate_button.set_sensitive(False) 62 | self.history = self.load_history() 63 | 64 | if any(item.get('selected', False) for item in self.history): 65 | GLib.idle_add(self.check_selected_item) 66 | else: 67 | self.results_stack.set_visible_child(self.empty_results) 68 | self.setup_fact_of_the_day() 69 | 70 | def setup_mask_dropdown(self): 71 | masks = [f"{i} - {self.calculator.int_to_dotted_netmask(i)}" for i in range(33)] 72 | self.mask_dropdown.set_model(Gtk.StringList.new(masks)) 73 | self.mask_dropdown.set_selected(24) 74 | 75 | expression = Gtk.PropertyExpression.new(Gtk.StringObject, None, "string") 76 | self.mask_dropdown.set_expression(expression) 77 | 78 | def connect_signals(self): 79 | self.calculate_button.connect("clicked", self.on_calculate_clicked) 80 | self.show_binary_switch.connect("notify::active", self.on_show_binary_changed) 81 | self.show_hex_switch.connect("notify::active", self.on_show_hex_changed) 82 | self.ip_entry.connect("changed", self.on_ip_entry_changed) 83 | self.ip_entry.get_delegate().connect("activate", self.on_ip_entry_activate) 84 | self.ip_entry_timeout_id = None 85 | 86 | if self.split_view: 87 | self.split_view.connect('notify::collapsed', self.on_split_view_state_changed) 88 | 89 | def setup_fact_of_the_day(self): 90 | facts = [ 91 | _('The 0.0.0.0 address is used to represent the default route or an unknown target network.'), 92 | _('A PTR record enables reverse DNS lookup, translating an IP address back to a domain name.'), 93 | _('The 240.0.0.0/4 block was originally reserved for future experiments, but is now considered legacy.'), 94 | _('In a /30 subnet, there are only 2 usable IP addresses, often used for point-to-point links.'), 95 | _('Wildcard mask inverts subnet masks, providing flexible IP filtering in access lists.'), 96 | _('An IP\'s binary representation always has 32 bits, regardless of the decimal notation used.') 97 | ] 98 | fact = choice(facts) 99 | self.fact_of_the_day_box.set_visible(True) 100 | self.fact_row.set_subtitle(fact) 101 | 102 | @Gtk.Template.Callback() 103 | def on_calculate_clicked(self, button=None): 104 | ip = self.ip_entry.get_text() 105 | mask = int(self.mask_dropdown.get_selected_item().get_string().split()[0]) 106 | 107 | new_item = {'ip': ip, 'mask': mask, 'selected': False} 108 | self.history = [item for item in self.history if not (item['ip'] == ip and item['mask'] == mask)] 109 | self.history.insert(0, new_item) 110 | self.history = self.history[:20] 111 | 112 | self.save_history() 113 | 114 | results = self.calculator.calculate(ip, mask) 115 | self.results = results 116 | self.display_results(results) 117 | self.fact_of_the_day_box.set_visible(False) 118 | 119 | if self.split_view: 120 | self.update_results_visibility(self.split_view.get_collapsed()) 121 | 122 | if self.history_dialog: 123 | self.update_history_list() 124 | self.update_clear_button_state() 125 | 126 | def display_results(self, results): 127 | self.results_stack.set_visible_child(self.results_stack.get_last_child()) 128 | 129 | while self.results_box.get_first_child(): 130 | self.results_box.remove(self.results_box.get_first_child()) 131 | while self.results_box_main.get_first_child(): 132 | self.results_box_main.remove(self.results_box_main.get_first_child()) 133 | 134 | def create_result_row(key, value): 135 | if value is None: 136 | return None 137 | subtitle = str(value) 138 | if key == _('Total Hosts'): 139 | count, *math = subtitle.split(' ', 1) 140 | subtitle = f"{count}{' ' + math[0] if math else ''}" 141 | row = Adw.ActionRow(title=key, subtitle=subtitle, subtitle_selectable=True) 142 | row.add_css_class("property-row") 143 | 144 | copy_button = Gtk.Button( 145 | icon_name="edit-copy-symbolic", 146 | valign=Gtk.Align.CENTER, 147 | tooltip_text=_('Copy') 148 | ) 149 | copy_button.add_css_class("flat") 150 | copy_value = value.replace('', '').replace('', '') if isinstance(value, str) else str(value) 151 | copy_button.connect("clicked", self.on_copy_clicked, copy_value) 152 | row.add_suffix(copy_button) 153 | return row 154 | 155 | for key, value in results.items(): 156 | row = create_result_row(key, value) 157 | if row: 158 | row_clone = create_result_row(key, value) 159 | self.results_box.append(row) 160 | self.results_box_main.append(row_clone) 161 | 162 | self.results_group.set_opacity(0) 163 | self.results_group_main.set_opacity(0) 164 | self.fade_in_results() 165 | 166 | def fade_in_results(self): 167 | for group in [self.results_group, self.results_group_main]: 168 | target = Adw.PropertyAnimationTarget.new(group, "opacity") 169 | animation = Adw.TimedAnimation.new(group, 0, 1, 350, target) 170 | animation.set_easing(Adw.Easing.EASE_OUT_CUBIC) 171 | animation.play() 172 | 173 | def on_show_binary_changed(self, switch, pspec): 174 | self.calculator.set_show_binary(switch.get_active()) 175 | if self.results_group_main.get_visible() or self.results_group.get_visible(): 176 | self.on_calculate_clicked(None) 177 | 178 | def on_show_hex_changed(self, switch, pspec): 179 | self.calculator.set_show_hex(switch.get_active()) 180 | if self.results_group_main.get_visible() or self.results_group.get_visible(): 181 | self.on_calculate_clicked(None) 182 | 183 | @Gtk.Template.Callback() 184 | def on_about_button_clicked(self, button): 185 | self.get_application().on_about_action(None, None) 186 | 187 | def on_copy_clicked(self, button, text): 188 | if '' in text: 189 | parts = text.split('') 190 | text = f"{parts[0].strip()} {parts[1].replace('', '').strip()}" 191 | clipboard = Gdk.Display.get_default().get_clipboard() 192 | clipboard.set(text) 193 | self.show_toast(_('Copied to clipboard')) 194 | 195 | def show_toast(self, message): 196 | toast = Adw.Toast.new(message) 197 | toast.set_timeout(2) 198 | self.toast_overlay.add_toast(toast) 199 | 200 | def on_ip_entry_changed(self, entry): 201 | if self.ip_entry_timeout_id: 202 | GLib.source_remove(self.ip_entry_timeout_id) 203 | self.ip_entry_timeout_id = GLib.timeout_add(0, self.delayed_ip_validation, entry) 204 | 205 | def delayed_ip_validation(self, entry): 206 | text = entry.get_text() 207 | valid_text = self.validate_ip_input(text) 208 | if valid_text != text: 209 | entry.set_text(valid_text) 210 | entry.set_position(-1) 211 | self.calculate_button.set_sensitive(self.is_valid_ip(valid_text)) 212 | self.ip_entry_timeout_id = None 213 | return GLib.SOURCE_REMOVE 214 | 215 | def validate_ip_input(self, text): 216 | parts = [''.join(filter(str.isdigit, part))[:3] for part in text.split('.')[:4]] 217 | valid_parts = [str(min(int(p), 255)) for p in parts if p] 218 | result = '.'.join(valid_parts) 219 | if text.endswith('.') and len(valid_parts) < 4: 220 | result += '.' 221 | return result 222 | 223 | def is_valid_ip(self, ip): 224 | octets = ip.split('.') 225 | if len(octets) != 4: 226 | return False 227 | for octet in octets: 228 | if not octet or not octet.isdigit() or int(octet) > 255: 229 | return False 230 | return True 231 | 232 | def on_ip_entry_activate(self, entry): 233 | if self.is_valid_ip(self.ip_entry.get_text()) and self.calculate_button.get_sensitive(): 234 | self.on_calculate_clicked(None) 235 | 236 | @Gtk.Template.Callback() 237 | def on_history_button_clicked(self, button): 238 | if not self.history_dialog: 239 | self.history_dialog = self.create_history_dialog() 240 | self.update_history_list() 241 | self.history_dialog.present(self) 242 | 243 | def create_history_dialog(self): 244 | dialog = Adw.Dialog() 245 | dialog.set_title(_('History')) 246 | dialog.set_size_request(350, 400) 247 | 248 | toolbar_view = Adw.ToolbarView() 249 | 250 | header_bar = Adw.HeaderBar() 251 | header_bar.add_css_class("flat") 252 | 253 | self.clear_button = Gtk.Button( 254 | icon_name="user-trash-symbolic", 255 | valign=Gtk.Align.CENTER, 256 | tooltip_text=_('Clear') 257 | ) 258 | self.clear_button.add_css_class("flat") 259 | self.clear_button.add_css_class("error") 260 | self.clear_button.connect("clicked", self.on_clear_history) 261 | header_bar.pack_start(self.clear_button) 262 | 263 | toolbar_view.add_top_bar(header_bar) 264 | 265 | self.history_list = Gtk.ListBox() 266 | self.history_list.set_selection_mode(Gtk.SelectionMode.NONE) 267 | self.history_list.add_css_class("boxed-list") 268 | 269 | self.empty_history_page = Adw.StatusPage( 270 | icon_name="document-open-recent-symbolic", 271 | title=_('No History'), 272 | description=_('Your calculation history will appear here') 273 | ) 274 | 275 | self.history_stack = Gtk.Stack() 276 | self.history_stack.add_named(self.history_list, "history") 277 | self.history_stack.add_named(self.empty_history_page, "empty") 278 | 279 | content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 280 | content_box.set_margin_top(16) 281 | content_box.set_margin_bottom(22) 282 | content_box.set_margin_start(16) 283 | content_box.set_margin_end(16) 284 | content_box.append(self.history_stack) 285 | 286 | scrolled_window = Gtk.ScrolledWindow( 287 | hscrollbar_policy=Gtk.PolicyType.NEVER, 288 | vscrollbar_policy=Gtk.PolicyType.AUTOMATIC, 289 | vexpand=True, 290 | child=content_box 291 | ) 292 | 293 | toolbar_view.set_content(scrolled_window) 294 | 295 | self.history_toast_overlay = Adw.ToastOverlay() 296 | self.history_toast_overlay.set_child(toolbar_view) 297 | 298 | dialog.set_child(self.history_toast_overlay) 299 | 300 | return dialog 301 | 302 | def update_clear_button_state(self): 303 | if hasattr(self, 'clear_button'): 304 | self.clear_button.set_sensitive(bool(self.history)) 305 | 306 | def update_history_list(self): 307 | while self.history_list.get_first_child(): 308 | self.history_list.remove(self.history_list.get_first_child()) 309 | 310 | if not self.history: 311 | self.history_stack.set_visible_child_name("empty") 312 | self.update_clear_button_state() 313 | return 314 | 315 | self.history_stack.set_visible_child_name("history") 316 | 317 | for item in self.history: 318 | title = f"{item['ip']}/{item['mask']}" 319 | mask_string = self.mask_dropdown.get_model().get_string(item['mask']).split('-', 1)[1].strip() 320 | 321 | row = Adw.ActionRow( 322 | title=title, 323 | subtitle=mask_string, 324 | activatable=True 325 | ) 326 | row.connect("activated", self.on_history_item_activated, item) 327 | 328 | use_button = Gtk.Button( 329 | icon_name="object-select-symbolic", 330 | valign=Gtk.Align.CENTER, 331 | tooltip_text=_('Select') 332 | ) 333 | use_button.add_css_class("flat") 334 | use_button.connect("clicked", self.on_history_item_activated, item) 335 | row.add_suffix(use_button) 336 | 337 | self.history_list.append(row) 338 | 339 | self.update_clear_button_state() 340 | 341 | def on_history_item_activated(self, widget, item): 342 | self.ip_entry.set_text(item['ip']) 343 | self.mask_dropdown.set_selected(item['mask']) 344 | self.history_dialog.close() 345 | self.on_calculate_clicked(None) 346 | self.show_toast(_('Calculated: {ip}/{mask}').format(ip=item['ip'], mask=item['mask'])) 347 | 348 | def on_clear_history(self, button): 349 | self.history.clear() 350 | self.save_history() 351 | self.update_history_list() 352 | toast = Adw.Toast.new(_('History cleared')) 353 | toast.set_timeout(2) 354 | self.history_toast_overlay.add_toast(toast) 355 | self.update_clear_button_state() 356 | 357 | def save_history(self): 358 | config_dir = GLib.get_user_config_dir() 359 | app_config_dir = path.join(config_dir, "netsleuth") 360 | makedirs(app_config_dir, exist_ok=True) 361 | history_file = path.join(app_config_dir, "history.json") 362 | 363 | with open(history_file, 'w') as f: 364 | dump(self.history, f, indent=4, ensure_ascii=False) 365 | 366 | def load_history(self): 367 | config_dir = GLib.get_user_config_dir() 368 | history_file = path.join(config_dir, "netsleuth", "history.json") 369 | 370 | if path.exists(history_file): 371 | with open(history_file, 'r') as f: 372 | return load(f) 373 | return [] 374 | 375 | def check_selected_item(self): 376 | selected_item = None 377 | 378 | for item in self.history: 379 | if item.get('selected', False): 380 | selected_item = item 381 | break 382 | 383 | if selected_item: 384 | self.history.remove(selected_item) 385 | self.history.insert(0, selected_item) 386 | self.ip_entry.set_text(selected_item['ip']) 387 | self.mask_dropdown.set_selected(selected_item['mask']) 388 | self.on_calculate_clicked(None) 389 | self.show_toast(_('Calculated: {ip}/{mask}').format(ip=selected_item['ip'], mask=selected_item['mask'])) 390 | selected_item['selected'] = False 391 | self.save_history() 392 | 393 | def do_close_request(self): 394 | self.save_history() 395 | return False 396 | 397 | @Gtk.Template.Callback() 398 | def on_copy_all_clicked(self, button): 399 | if not hasattr(self, "results") or not self.results: 400 | return 401 | 402 | text = "\n".join(f"{key}: {self.format_value(value, exclude_math=True)}" 403 | for key, value in self.results.items() 404 | if value is not None) 405 | clipboard = Gdk.Display.get_default().get_clipboard() 406 | clipboard.set(text) 407 | self.show_toast(_('Copied to clipboard')) 408 | 409 | def remove_math_formula(self, value): 410 | if isinstance(value, str): 411 | parts = value.split(' ', 1) 412 | if len(parts) > 1 and parts[1].startswith('(2'): 413 | return parts[0] 414 | return value 415 | 416 | def format_value(self, value, exclude_math=False): 417 | if isinstance(value, str): 418 | if '' in value: 419 | parts = value.split('') 420 | formatted_parts = [parts[0].strip()] 421 | if len(parts) > 1: 422 | formatted_parts.append(f"({parts[1].replace('', '').strip()})") 423 | if len(parts) > 2: 424 | formatted_parts.append(f"({parts[2].replace('', '').strip()})") 425 | return ' '.join(formatted_parts) 426 | if exclude_math: 427 | return self.remove_math_formula(value) 428 | return str(value) 429 | 430 | @Gtk.Template.Callback() 431 | def on_export_clicked(self, button): 432 | if not hasattr(self, "results") or not self.results: 433 | return 434 | 435 | dialog = Gtk.FileChooserNative.new( 436 | title=_('Export results'), 437 | parent=self, 438 | action=Gtk.FileChooserAction.SAVE 439 | ) 440 | dialog.set_current_name("results.json") 441 | 442 | filter_json = Gtk.FileFilter() 443 | filter_json.add_mime_type("application/json") 444 | dialog.add_filter(filter_json) 445 | 446 | dialog.connect("response", self.on_export_response) 447 | dialog.show() 448 | 449 | def format_value_for_export(self, value): 450 | if isinstance(value, str): 451 | if '' in value: 452 | parts = value.split('') 453 | remaining_parts = parts[1:] 454 | current_part = 0 455 | result = {'decimal': parts[0].strip()} 456 | 457 | if self.calculator.show_binary: 458 | if current_part < len(remaining_parts): 459 | result['binary'] = remaining_parts[current_part].replace('', '').strip() 460 | current_part += 1 461 | 462 | if self.calculator.show_hex: 463 | if current_part < len(remaining_parts): 464 | result['hexadecimal'] = remaining_parts[current_part].replace('', '').strip() 465 | 466 | return result 467 | 468 | return self.remove_math_formula(value) 469 | 470 | def on_export_response(self, dialog, response): 471 | if response == Gtk.ResponseType.ACCEPT: 472 | file = dialog.get_file() 473 | file_path = file.get_path() 474 | file_name = file.get_basename() 475 | 476 | mappings = { 477 | _('Address'): 'Address', 478 | _('Netmask'): 'Netmask', 479 | _('Wildcard'): 'Wildcard', 480 | _('Network'): 'Network', 481 | _('Broadcast'): 'Broadcast', 482 | _('First Host'): 'First Host', 483 | _('Last Host'): 'Last Host', 484 | _('Total Hosts'): 'Total Hosts', 485 | _('Category'): 'Category', 486 | _('PTR Record'): 'PTR Record', 487 | _('IPv4 Mapped Address'): 'IPv4 Mapped Address', 488 | _('6to4 Prefix'): '6to4 Prefix', 489 | _('Private (Class A)'): 'Private (Class A)', 490 | _('Private (Class B)'): 'Private (Class B)', 491 | _('Private (Class C)'): 'Private (Class C)', 492 | _('Loopback'): 'Loopback', 493 | _('Link-Local (APIPA)'): 'Link-Local (APIPA)', 494 | _('Multicast'): 'Multicast', 495 | _('Reserved'): 'Reserved', 496 | _('Public'): 'Public' 497 | } 498 | 499 | export_results = { 500 | mappings.get(key, key): ( 501 | mappings.get(value, value) if mappings.get(key, key) == 'Category' and isinstance(value, str) 502 | else self.format_value_for_export(value) 503 | ) 504 | for key, value in self.results.items() 505 | if value is not None 506 | } 507 | 508 | with open(file_path, 'w', encoding='utf-8') as f: 509 | dump(export_results, f, ensure_ascii=False, indent=2) 510 | 511 | self.show_toast(_('Saved to {file}').format(file=file_name)) 512 | 513 | dialog.destroy() 514 | 515 | def update_results_visibility(self, is_collapsed): 516 | if is_collapsed: 517 | self.results_group_main.set_visible(True) 518 | self.results_group.set_visible(False) 519 | self.split_view.set_show_content(False) 520 | else: 521 | self.results_group_main.set_visible(False) 522 | self.results_group.set_visible(True) 523 | self.split_view.set_show_content(True) 524 | 525 | def on_split_view_state_changed(self, split_view, pspec): 526 | if not hasattr(self, 'results') or not self.results: 527 | return 528 | 529 | self.update_results_visibility(split_view.get_collapsed()) 530 | -------------------------------------------------------------------------------- /src/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 276 | 277 | --------------------------------------------------------------------------------