├── COPYING ├── NEWS.md ├── README.md ├── ROADMAP.md ├── drop-down-terminal@gs-extensions.zzrough.org ├── convenience.js ├── extension.js ├── gschemas.compiled ├── gtk-3-14.css ├── gtk-3-16.css ├── gtk.css ├── metadata.json ├── org.zzrough.gs-extensions.drop-down-terminal.gschema.xml ├── prefs.gtkbuilder ├── prefs.js ├── stylesheet.css └── terminal.js ├── screenshot-prefs-1.png ├── screenshot-prefs-2.png └── screenshot-term.png /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 | . 675 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | v1 (2012-09-05) 2 | - initial, basic version 3 | 4 | v2 (2012-09-22) 5 | - support for 3.5.92 (3.6 was already targetd in v1) 6 | 7 | v3 (2012-10-06) 8 | - added an animation 9 | - added a border to mimick the shell style 10 | - added preferences (animation, height, shortcut) 11 | - fixed the default key to really be the Above_Tab key, not F12 (thanks to Obsidien for spotting that) 12 | - fixed the focus issue (way~~~~~~~~~ better) (the cursor is still not in block form though) 13 | - fixed link activation 14 | - stop opening directly the terminal in the child process by watching the bus name appearance. 15 | this simplifies shell restart survival a lot 16 | 17 | v4 (2012-10-19) 18 | - feature: [multihead support, always open on the primary monitor (github #1)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/1) 19 | - feature: copy/paste via popupmenu + shortcuts (ctrl-shift-C/V) 20 | - feature: show preferences on first startup 21 | - bugfix: [use the system monospace font (github #3)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/3) 22 | - bugfix: [ZSH (github #2)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/2) 23 | 24 | v5 (2012-10-25) 25 | - feature: check for dependencies at startup (asked by l300lvl -- Debian distributes gir files in their own packages) 26 | - feature: custom command (suggested by sequentious, to be able to always run 'mutt' or 'screen' for instance) 27 | - bugfix: [terminal on wrong monitor (github #10)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/10) 28 | - bugfix: better animation depending on monitor position (animate the height instead of the position if there is a monitor above the primary one) 29 | - bugfix: glitch at animation startup (the window can be briefly seen at the wrong location) 30 | - code: investigate if the inner-border can be retrieved 31 | 32 | v6 (2012-11-05) 33 | - support for 3.7.1 34 | - bugfix: [terminal lost after screen is locked (github #6)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/6) 35 | 36 | v7 (2012-12-08) 37 | - support for 3.7.2 38 | - feature: [opaque terminal (github #12)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/12) 39 | - feature: option to show the scrollbar (suggested by bhaveekdesai) 40 | - bugfix: set the opacity of the terminal instead of the window to improve text readibility 41 | - bugfix: [use gnome shell style context menu (github #11)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/11) 42 | - code: use settings directly instead of the dbus API 43 | 44 | v8 (2012-12-17) 45 | - bugfix: [Screen unlock makes terminal appear (github #21)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/21) 46 | 47 | v9 (2013-03-25) 48 | - support from 3.7.3 up to 3.8 49 | - workaround for a gtk allocation regression [b.g.o #696187](https://bugzilla.gnome.org/show_bug.cgi?id=696187) 50 | - workaround for a mutter 3.7.90 change (possibly the frame sync) 51 | - feature: improve the ctrl-alt-tab look to match the shell high-res symbolic icons 52 | - feature: way faster opening (an invisible animation was delaying the opening animation for 0.25s) 53 | - feature: proper transparency for the terminal+scrollbar thanks to the new gtk 3.7 opacity handling 54 | - feature: [Separate opening and closing animation (github #27)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/27) 55 | - feature: [Customizable transparency level (github #30)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/30) 56 | - bugfix: [no focus given on ctrl-alt-tab (github #8)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/8) 57 | - bugfix: [Small gap above terminal (github #15)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/15) 58 | 59 | v10 (2013-04-21) 60 | - bugfix: [Allow a block or underline cursor in the terminal (github #36)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/36) 61 | - bugfix: [Switch focus faster (github #38)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/38) 62 | 63 | v11 (2013-04-28) 64 | - bugfix: [Error upgrading extension (github #40)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/40) 65 | - code: make the gtk allocation regression fix conditional ; the story is not finished yet see b.g.o. #696882 66 | 67 | v12 (2013-05-08) 68 | - bugfix: [Height change doesn''t work on 3.8.1 (github #41)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/41) 69 | 70 | v13 (2013-09-25) 71 | - support up to 3.10 72 | - disable use of very recently deprecated methods in Vte as the methods are not visible from GJS 73 | 74 | v14 (2014-03-26) 75 | - support up to 3.12 76 | - fix terminal jumping to secondary monitor (thanks to Martin Pöhlmann!) 77 | - tentative fix for transparency loss with utter failure 78 | 79 | v15 (2014-09-23) 80 | - support up to 3.14 81 | 82 | v16 (2014-09-27) 83 | - enabled back transparency as certain distributions support it again (mainly Fedora) 84 | 85 | v17 (2015-03-25) 86 | - toggle the terminal by scrolling on the top panel (thanks to Kristoffer Kleine) 87 | - added settings to set foreground/background color (thanks to kraiz) 88 | - make terminal opacity configurable (thanks to Tonic Artos) 89 | - fixed a wrong vertical offset (thanks to Alexander Shabalin) 90 | - port to Gnome 3.16 (thanks to Ádám Barancsuk) 91 | - added prefs for foreground/background color 92 | - fixed terminal position on HiDPI 93 | - fixed space at the top when top panel hidden 94 | - only keep compat with Gnome-Shell 3.10+ (older versions are becoming a huge maintenance burden) 95 | - port prefs gtkbuilder file to gtk3 96 | 97 | v18 (2015-09-23) 98 | - fix for broken context menu in 3.16 (thanks to nikolowry) 99 | - match 3.16+ menu border (not the thick gray border anymore) 100 | 101 | v19 (2015-11-04) 102 | - fix animation and positioning on 3.18 (thanks to imnotjames and daniviga) 103 | 104 | v20 (2016-03-25) 105 | - support up to 3.20 106 | - add configurable position (top/bottom/left/right) (thanks to TonicArtos) 107 | 108 | v21 (2016-08-17) 109 | - prepare support for 3.22 (thanks to MedicMomcilo) 110 | - add preference to toggle audible bell (thanks to Simon Flack) 111 | - refresh the readme screenshots (thanks to Dennis Schürholz) 112 | - fixed dash-to-dock compatibility (thanks to Tom Gehrke and Dennis Schürholz) 113 | - update CSS for gtk 3.20+ (thanks to Rom Grk) 114 | - new tips/scripts in the wiki (thanks to Rom Grk) 115 | - use the new gtk3 scrollbars (better look & feel) 116 | - drop support for gnome 3.12 (even Debian is on 3.13 since Jessie) 117 | 118 | v22 (2016-08-29) 119 | - bugfix: [regression on 3.14 (github #158)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/158) 120 | 121 | v23 (2017-01-23) 122 | - support for 3.22 (thanks to palopezv) 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gs-extensions-drop-down-terminal 2 | ================================ 3 | 4 | Drop Down Terminal extension for the Gnome Shell 5 | 6 | Screenshots 7 | ----------- 8 | 9 | ### Terminal 10 | ![Terminal](https://github.com/zzrough/gs-extensions-drop-down-terminal/raw/master/screenshot-term.png) 11 | 12 | ### Preferences 13 | ![Preferences](https://github.com/zzrough/gs-extensions-drop-down-terminal/raw/master/screenshot-prefs-1.png) 14 | ![Preferences](https://github.com/zzrough/gs-extensions-drop-down-terminal/raw/master/screenshot-prefs-2.png) 15 | 16 | Additional shortcuts 17 | -------------------- 18 | 19 | On top of the main shortcut to display/hide the terminal, there are additional shortcuts that can not be changed: 20 | 21 | * Ctrl-Shift-C: copy 22 | * Ctrl-Shift-V: paste 23 | 24 | Additional configuration and tips 25 | --------------------------------- 26 | 27 | See the wiki [for more configuration and tips](https://github.com/zzrough/gs-extensions-drop-down-terminal/wiki). 28 | 29 | 30 | Bug reporting 31 | ------------- 32 | 33 | Use the [Github issue tracker](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues) to report issues 34 | or ask for features **matching the vision** of this extension, in the next paragraph. 35 | 36 | Vision 37 | ------ 38 | 39 | I want a terminal that fits in the gnome-shell spirit: elegant, fast, simple, straight to the point. 40 | 41 | It is an extension (easy to install), it provides a nice default shortcut, a fast, but non-disruptive animation, 42 | does not consume anything if never used, etc. but **it will not get all the features of gnome-terminal**, that really is 43 | not the point. Use guake or yakuake instead if you want a full-fledged equivalent (albeit looking less integrated). 44 | 45 | Of course, I'm opened to suggestions, but please do not ask for ultra advanced settings nobody will ever need, 46 | the code is already complex enough for reviewers to handle. 47 | 48 | The only "controversial" feature I'm enclined to add at this point is multiple tabs support, because I myself am 49 | used to it, but even I can open a gnome-terminal with multiple tabs already for long working sessions: that 50 | complements this extension quite well. But it will not impact regular users with no tab: the terminal must remain 51 | slick by default. 52 | 53 | As a side note, a lot of things can **not** be done with bindings and thus extensions. 54 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | v24 2 | - bugfix: there is a slight 1px border on the scrollbar gutter (I tried removing it, works within gtk-inspector but not at runtime) 3 | 4 | v25 5 | - feature: [better height preference UI (github #5)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/5) 6 | - feature: customizable font 7 | - feature: detect active pid and ask before exiting the shell? (or detach it?) 8 | - feature: remove the border when height = 100% 9 | - code: investigate argb colors not working 10 | 11 | v26 12 | - feature: prompt to kill the possible active pid on logout to avoid loosing something 13 | - bugfix: [when switching Workspaces DD Terminal flashes in and out (github #4)](https://github.com/zzrough/gs-extensions-drop-down-terminal/issues/4) 14 | - code: investigate how to have proper theming of the border (using the generic popup border or at least make it customisable through CSS) 15 | 16 | v27 17 | - feature (maybe): autohide (asked by step-2, koji3) 18 | - feature (maybe): multi tabs (13 votes for, 10 votes against) 19 | - feature (maybe): allow custom width (suggested by Dreamsorcerer) 20 | - feature (maybe): dnd (already tried, not that simple) 21 | - bugfix: investigate the focus issue preventing using the BLOCK cursor 22 | 23 | future 24 | - feature: pause the child process if term closed and no active pid (for powersaving, maybe an additional pref?) 25 | -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/convenience.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 Stéphane Démurget 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | // Author: Stéphane Démurget 17 | 18 | const Lang = imports.lang; 19 | const MainLoop = imports.mainloop; 20 | 21 | const Gdk = imports.gi.Gdk; 22 | const Gio = imports.gi.Gio; 23 | const GLib = imports.gi.GLib; 24 | const Gtk = imports.gi.Gtk; 25 | 26 | 27 | const GLIB_VERSION = GLib.MAJOR_VERSION * 10000 28 | + GLib.MINOR_VERSION * 100 29 | + GLib.MICRO_VERSION; 30 | 31 | const GTK_VERSION = Gtk.MAJOR_VERSION * 10000 32 | + Gtk.MINOR_VERSION * 100 33 | + Gtk.MICRO_VERSION; 34 | 35 | 36 | function getSettings(extensionPath, extensionId) { 37 | let defaultSource = Gio.SettingsSchemaSource.get_default(); 38 | let source = Gio.SettingsSchemaSource.new_from_directory(extensionPath, defaultSource, false); // trusted = false 39 | 40 | let schemaId = 'org.zzrough.gs-extensions.' + extensionId; 41 | let schema = source.lookup(schemaId, false); // recursive = false 42 | 43 | if (!schema) { 44 | throw new Error("Schema " + schemaId + " could not be found in the path " + extensionPath); 45 | } 46 | 47 | return new Gio.Settings({ 48 | settings_schema: schema 49 | }); 50 | } 51 | 52 | function getInstalledSettings(schema) { 53 | let installedSchemas = Gio.Settings.list_schemas(); 54 | 55 | for (let i in installedSchemas) { 56 | if (installedSchemas[i] == schema) { 57 | return Gio.Settings.new(installedSchemas[i]); 58 | } 59 | } 60 | 61 | return null; 62 | } 63 | 64 | /* 65 | * Throttles a function call in the mainloop. 66 | * 67 | * This delays the call of the function @func and compresses calls to a maximum of one in the 68 | * given @interval. 69 | * 70 | * This is especially useful to delay the application of a setting. 71 | * 72 | * @interval: the application interval in milliseconds 73 | * @scope: the execution scope of the function 74 | * @func: the delegate function to call in the mainloop 75 | * @args: the possible args, or null or undefined if no argument should be passed 76 | */ 77 | function throttle(interval, scope, func, args) { 78 | if (func._throttlingId !== undefined) { 79 | MainLoop.source_remove(func._throttlingId); 80 | } 81 | 82 | func._throttlingId = MainLoop.timeout_add(interval, function() { 83 | func.apply(scope, args); 84 | delete func._throttlingId; 85 | 86 | return false; 87 | }); 88 | } 89 | 90 | 91 | /** 92 | * Adds the specified idle function to be processed by the GDK thread, with 93 | * the default priority (GLib.PRIORITY_DEFAULT_IDLE). 94 | * 95 | * @func: the function to execute within the GDK lock 96 | */ 97 | function runInGdk(func) { 98 | Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, func); 99 | } 100 | 101 | 102 | /** 103 | * Creates a runner used to execute a function in the GDK thread. 104 | * 105 | * @func: the function to execute within the GDK lock 106 | */ 107 | function gdkRunner(func) { 108 | return function() { 109 | runInGdk(func); 110 | }; 111 | } 112 | 113 | 114 | /** 115 | * Returns the pid of the active process. 116 | * 117 | * @return the pid 118 | */ 119 | function getPid() { 120 | return new Gio.Credentials().get_unix_pid(); 121 | } 122 | 123 | 124 | /** 125 | * Parses a Gdk.RGBA color of the form "#rgb", 126 | * "#rrggbb", "#rrrgggbbb" or "#rrrrggggbbbb". 127 | * 128 | * @return the color parsed 129 | */ 130 | function parseRgbaColor(spec) { 131 | let col = new Gdk.RGBA(); 132 | col.parse(spec); 133 | return col; 134 | } 135 | 136 | -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/extension.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 Stéphane Démurget 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | // Author: Stéphane Démurget 17 | 18 | const Lang = imports.lang; 19 | const Gettext = imports.gettext.domain("drop-down-terminal"); 20 | const Mainloop = imports.mainloop; 21 | 22 | const Clutter = imports.gi.Clutter; 23 | const Cogl = imports.gi.Cogl; 24 | const Gdk = imports.gi.Gdk; 25 | const GdkX11 = imports.gi.GdkX11; 26 | const Gio = imports.gi.Gio; 27 | const GLib = imports.gi.GLib; 28 | const Gtk = imports.gi.Gtk; 29 | const Meta = imports.gi.Meta; 30 | const Pango = imports.gi.Pango; 31 | const St = imports.gi.St; 32 | const Shell = imports.gi.Shell; 33 | 34 | const Main = imports.ui.main; 35 | const ModalDialog = imports.ui.modalDialog; 36 | const Tweener = imports.ui.tweener; 37 | 38 | const _ = Gettext.gettext; 39 | const Config = imports.misc.config; 40 | const ExtensionSystem = imports.ui.extensionSystem; 41 | const ExtensionUtils = imports.misc.extensionUtils; 42 | const Me = imports.misc.extensionUtils.getCurrentExtension(); 43 | const Convenience = Me.imports.convenience; 44 | 45 | 46 | // constants 47 | const ANIMATION_CONFLICT_EXTENSION_UUIDS = [ 48 | "window-open-animation-rotate-in@mengzhuo.org", 49 | "window-open-animation-slide-in@mengzhuo.org", 50 | "window-open-animation-scale-in@mengzhuo.org" 51 | ]; 52 | 53 | const TERMINAL_WINDOW_ACTOR_NAME = "dropDownTerminalWindow"; 54 | const TERMINAL_WINDOW_WM_CLASS = "DropDownTerminalWindow"; 55 | const DEBUG = false; 56 | 57 | const FIRST_START_SETTING_KEY = "first-start"; 58 | const ENABLE_ANIMATION_SETTING_KEY = "enable-animation"; 59 | const OPENING_ANIMATION_TIME_SETTING_KEY = "opening-animation-time"; 60 | const CLOSING_ANIMATION_TIME_SETTING_KEY = "closing-animation-time"; 61 | const TERMINAL_SIZE_SETTING_KEY = "terminal-size"; 62 | const REAL_SHORTCUT_SETTING_KEY = "real-shortcut"; 63 | const ENABLE_TOGGLE_ON_SCROLL_SETTING_KEY = "enable-toggle-on-scroll"; 64 | const TERMINAL_POSITION_SETTING_KEY = "terminal-position"; 65 | 66 | const TOP_EDGE = 0; 67 | const LEFT_EDGE = 1; 68 | const RIGHT_EDGE = 2; 69 | const BOTTOM_EDGE = 3; 70 | 71 | const SHELL_VERSION = 10 * parseFloat("0." + Config.PACKAGE_VERSION.split(".").join("")).toFixed(10); 72 | 73 | // dbus interface 74 | const DropDownTerminalIface = 75 | ' \ 76 | \ 77 | \ 78 | \ 79 | \ 80 | \ 81 | \ 82 | \ 83 | \ 84 | \ 85 | \ 86 | \ 87 | \ 88 | \ 89 | \ 90 | \ 91 | \ 92 | '; 93 | 94 | 95 | // helper to only log in debug mode 96 | function debug(text) { DEBUG && log("[DDT] " + text); } 97 | 98 | 99 | // window border effect class 100 | // 101 | // we should use a delegate to avoid the GType crashes (https://bugzilla.gnome.org/show_bug.cgi?id=688973) 102 | // but we can't since the Clutter.Effect is abstract, so let's add a crappy hack there 103 | if (window.__DDTInstance === undefined) { 104 | window.__DDTInstance = 1; 105 | } 106 | 107 | const SouthBorderEffect = new Lang.Class({ 108 | Name: "SouthBorderEffect-" + window.__DDTInstance++, 109 | Extends: Clutter.Effect, 110 | 111 | _init: function() { 112 | this.parent(); 113 | 114 | this._color = new Cogl.Color(); 115 | 116 | if (Convenience.GTK_VERSION >= 31590) { 117 | this._color.init_from_4ub(0x1c, 0x1f, 0x1f, 0xff); 118 | this._width = 1; 119 | } else { 120 | this._color.init_from_4ub(0xa5, 0xa5, 0xa5, 0xff); 121 | this._width = 2; 122 | } 123 | }, 124 | 125 | vfunc_paint: function() { 126 | let actor = this.get_actor(); 127 | let geom = actor.get_allocation_geometry(); 128 | 129 | actor.continue_paint(); 130 | 131 | Cogl.set_source_color(this._color); 132 | Cogl.rectangle(0, geom.height, geom.width, geom.height - this._width); 133 | }, 134 | }); 135 | 136 | 137 | // missing dependencies dialog 138 | const MissingVteDialog = new Lang.Class({ 139 | Name: "MissingDepsDialog", 140 | 141 | _init: function() { 142 | this._delegate = new ModalDialog.ModalDialog({styleClass: "modal-dialog"}); 143 | 144 | this._delegate.setButtons([{ label: _("Close"), 145 | action: Lang.bind(this._delegate, this._delegate.close), 146 | key: Clutter.Escape, 147 | default: true 148 | }]); 149 | 150 | let errorIcon = new St.Icon({ icon_name: "dialog-error-symbolic", 151 | icon_size: 24, 152 | style_class: "run-dialog-error-icon" }); 153 | 154 | let titleLabel = new St.Label({ text: _("Vte library missing") }); 155 | let messageLabel = new St.Label({ text: _("The 'Drop Down Terminal' extension requires the Vte library (version >= 0.31) and its gir typelib.\n" + 156 | "\n" + 157 | "Please install:\n" + 158 | " - on Fedora/Arch: the 'vte3' package (you certainly uninstalled it)\n" + 159 | " - on Debian/Ubuntu: the 'gir-1.2-vte-2.*' package (not installed by default)\n" + 160 | " - on openSUSE: the 'typelib-1_0-Vte-2.*' package (not installed by default)\n" + 161 | "\n" + 162 | "Then, log out.") }); 163 | messageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; 164 | messageLabel.clutter_text.line_wrap = true; 165 | 166 | let titleBox = new St.BoxLayout(); 167 | titleBox.add(errorIcon); 168 | titleBox.add(new St.Label({ text: ' ' })); 169 | titleBox.add(titleLabel, { x_fill: true }); 170 | 171 | let box = new St.BoxLayout({ vertical: true }); 172 | box.add(titleBox); 173 | box.add(new St.Label({ text: ' ' })); 174 | box.add(messageLabel); 175 | 176 | this._delegate.contentLayout.add(box); 177 | }, 178 | 179 | open: function() { 180 | this._delegate.open(); 181 | } 182 | }); 183 | 184 | 185 | // extension class 186 | const DropDownTerminalExtension = new Lang.Class({ 187 | Name: "DropDownTerminalExtension", 188 | 189 | _init: function() { 190 | // retrieves the settings 191 | this._settings = Convenience.getSettings(Me.path, Me.metadata.id); 192 | }, 193 | 194 | enable: function() { 195 | // initializes the child pid and bus proxy members early as it used to know if it has been spawn already 196 | this._childPid = null; 197 | 198 | // initializes other members used to toggle the terminal 199 | this._busProxy = null; 200 | this._windowActor = null; 201 | this._firstDisplay = true; 202 | 203 | // initializes if we should toggle on bus name appearance 204 | this._toggleOnBusNameAppearance = false; 205 | 206 | // check dependencies 207 | this._checkDependencies(); 208 | 209 | // animation setup 210 | this._display = global.screen ? global.screen.get_display() : global.display; 211 | this._windowCreatedHandlerId = this._display.connect("window-created", Lang.bind(this, this._windowCreated)); 212 | this._actorMappedHandlerId = global.window_manager.connect("map", Lang.bind(this, this._windowMapped)); 213 | 214 | // geometry update on monitor configuration change or panel size change 215 | this._monitorsChangedHandlerId = Main.layoutManager.connect("monitors-changed", Lang.bind(this, function() { 216 | Convenience.throttle(100, this, this._updateWindowGeometry); // throttles at 10Hz (it's an "heavy weight" setting) 217 | })); 218 | 219 | this._panelAllocationNotificationHandlerId = Main.layoutManager.panelBox.connect("notify::allocation", Lang.bind(this, function() { 220 | Convenience.throttle(100, this, this._updateWindowGeometry); // throttles at 10Hz (it's an "heavy weight" setting) 221 | })); 222 | 223 | this._panelScrollEventHandlerId = Main.panel.actor.connect("scroll-event", Lang.bind(this, this._panelScrolled)); 224 | 225 | // honours setting changes 226 | this._settingChangedHandlerIds = [ 227 | this._settings.connect("changed::" + ENABLE_ANIMATION_SETTING_KEY, Lang.bind(this, this._updateAnimationProperties)), 228 | this._settings.connect("changed::" + OPENING_ANIMATION_TIME_SETTING_KEY, Lang.bind(this, this._updateAnimationProperties)), 229 | this._settings.connect("changed::" + CLOSING_ANIMATION_TIME_SETTING_KEY, Lang.bind(this, this._updateAnimationProperties)), 230 | this._settings.connect("changed::" + ENABLE_TOGGLE_ON_SCROLL_SETTING_KEY, Lang.bind(this, this._updateToggleOnScroll)), 231 | 232 | this._settings.connect("changed::" + TERMINAL_SIZE_SETTING_KEY, Lang.bind(this, function() { 233 | if (this._windowActor !== null) { 234 | debug("size changed"); 235 | this._windowActor.remove_clip(); 236 | Convenience.throttle(100, this, this._updateWindowGeometry); // throttles at 10Hz (it's an "heavy weight" setting) 237 | } 238 | })), 239 | 240 | this._settings.connect("changed::" + TERMINAL_POSITION_SETTING_KEY, Lang.bind(this, function() { 241 | if (this._windowActor !== null) { 242 | debug("position changed"); 243 | this._windowActor.remove_clip(); 244 | Convenience.throttle(100, this, this._updateWindowGeometry); // throttles at 10Hz (it's an "heavy weight" setting) 245 | } 246 | })), 247 | 248 | this._settings.connect("changed::" + REAL_SHORTCUT_SETTING_KEY, Lang.bind(this, function() { 249 | this._unbindShortcut(); 250 | this._bindShortcut(); 251 | })) 252 | ]; 253 | 254 | // applies the settings initially 255 | this._updateAnimationProperties(); 256 | this._updateToggleOnScroll(); 257 | this._updateWindowGeometry(); 258 | this._bindShortcut(); 259 | 260 | // registers the bus name watch 261 | this._busWatchId = Gio.DBus.session.watch_name("org.zzrough.GsExtensions.DropDownTerminal", 262 | Gio.BusNameWatcherFlags.NONE, 263 | Lang.bind(this, this._busNameAppeared), 264 | Lang.bind(this, this._busNameVanished), 265 | null, null); 266 | 267 | // change the ctrl-alt-tab popup switcher to ignore our window as we will handle it ourself 268 | // (for the look and also because the focus switching needs a hack in our case) 269 | this._oldCtrlAltTabManagerPopupFunc = Main.ctrlAltTabManager.popup; 270 | 271 | Main.ctrlAltTabManager.popup = Lang.bind(this, function(backward, binding, mask) { 272 | let oldGetTabList = this._display.get_tab_list; 273 | 274 | this._display.get_tab_list = Lang.bind(this, function(type, screen, workspace) { 275 | let windows = Lang.bind(this._display, oldGetTabList)(type, screen, workspace); 276 | windows = windows.filter(function(win) { return win.get_wm_class() != TERMINAL_WINDOW_WM_CLASS; }); 277 | return windows; 278 | }); 279 | 280 | Lang.bind(Main.ctrlAltTabManager, this._oldCtrlAltTabManagerPopupFunc)(backward, binding, mask); 281 | 282 | this._display.get_tab_list = oldGetTabList; 283 | }); 284 | 285 | let focusManager = global.focus_manager; 286 | this._oldFocusManagerAddGroupFunc = focusManager.add_group; 287 | this._oldFocusManagerRemoveGroupFunc = focusManager.remove_group; 288 | 289 | // finds our window out if we come back from the locking screen for instance 290 | let windowActors = global.get_window_actors(); 291 | 292 | for (let i in windowActors) { 293 | if (windowActors[i].get_meta_window().get_wm_class() == TERMINAL_WINDOW_WM_CLASS) { 294 | this._setWindowActor(windowActors[i]); 295 | break; 296 | } 297 | } 298 | 299 | // handles the first start 300 | this._handleFirstStart(); 301 | }, 302 | 303 | disable: function() { 304 | // unbinds the shortcut 305 | this._unbindShortcut(); 306 | 307 | // removes the ctrl-alt-tab group 308 | if (this._windowActor !== null) { 309 | Main.ctrlAltTabManager.removeGroup(this._windowActor); 310 | } 311 | 312 | // restores the monkey patched functions 313 | Main.ctrlAltTabManager.popup = this._oldCtrlAltTabManagerPopupFunc; 314 | global.focus_manager.add_group = this._oldFocusManagerAddGroupFunc 315 | global.focus_manager.remove_group = this._oldFocusManagerRemoveGroupFunc 316 | 317 | // issue #6: Terminal lost after screen is locked 318 | // 319 | // gnome-shell 3.6 introduces a new screen shield that disables all extensions: this is not a problem 320 | // for almost all extensions but this is a real killer for this one as the terminal must quit on disable! 321 | // 322 | // the extension already rebinds with the terminal on enable, so we just need not to quit the terminal 323 | // if the screen is getting locked 324 | let lockingScreen = (Main.sessionMode.currentMode == "unlock-dialog" // unlock-dialog == shield/curtain (before lock-screen w/ gdm) 325 | || Main.sessionMode.currentMode == "lock-screen"); // lock-screen == lock screen (after unlock-dialog or w/o gdm) 326 | 327 | // checks if there is not an instance of a previous child, mainly because it survived a shell restart 328 | // (the shell reexec itself thus not letting the extensions a chance to properly shut down) 329 | if (this._childPid === null && this._busProxy !== null) { 330 | this._childPid = this._busProxy["Pid"]; 331 | } 332 | 333 | // quit and/or kill the child process if it exists, except if we are going to the lock 334 | // screen, as the user will obviously unlock and he expects his terminal back 335 | if (!lockingScreen && this._childPid !== null) { 336 | try { 337 | // starts by asking to quit gracefully 338 | this._quitingChild = true; 339 | this._busProxy.QuitRemote(); 340 | 341 | // if the remote call succeeded, we forget about this process 342 | this._childPid = null; 343 | } catch (e) { 344 | debug("error asking the terminal to quit gracefully (cause: " + e.name + " - " + e.message + ")"); 345 | 346 | // quiting failed, so mark it and kills the process 347 | this._quitingChild = false; 348 | this._killChild(); 349 | } 350 | } 351 | 352 | // unregister the bus name watch 353 | Gio.DBus.session.unwatch_name(this._busWatchId); 354 | this._busWatchId = null; 355 | 356 | // disconnects the setting listeners 357 | for (let i in this._settingChangedHandlerIds) { 358 | this._settings.disconnect(this._settingChangedHandlerIds[i]); 359 | } 360 | 361 | this._settingChangedHandlerIds = null; 362 | 363 | // disconnects signals and clears refs related to screen handling 364 | global.window_manager.disconnect(this._actorMappedHandlerId); 365 | Main.layoutManager.disconnect(this._monitorsChangedHandlerId); 366 | Main.layoutManager.panelBox.disconnect(this._panelAllocationNotificationHandlerId); 367 | Main.panel.actor.disconnect(this._panelScrollEventHandlerId); 368 | this._display.disconnect(this._windowCreatedHandlerId); 369 | this._actorMappedHandlerId = null; 370 | this._monitorsChangedHandlerId = null; 371 | this._panelAllocationNotificationHandlerId = null; 372 | this._panelScrollEventHandlerId = null; 373 | this._windowCreatedHandlerId = null; 374 | this._windowActor = null; 375 | this._display = null; 376 | }, 377 | 378 | _toggle: function() { 379 | debug("asked to toggle"); 380 | 381 | // checks if there is not an instance of a previous child, mainly because it survived a shell restart 382 | // (the shell reexec itself thus not letting the extensions a chance to properly shut down) 383 | if (this._childPid === null && this._busProxy !== null) { 384 | this._childPid = this._busProxy["Pid"]; 385 | } 386 | 387 | // forks if the child does not exist (never started or killed) 388 | if (this._childPid === null) { 389 | debug("forking and connecting to the terminal dbus interface"); 390 | 391 | this._toggleOnBusNameAppearance = true; 392 | this._forkChild(); 393 | } 394 | 395 | // FIXME: pull request #69, we should really not have to do that as we already monitor 396 | // the "monitors-changed" signal 397 | this._updateWindowGeometry(); 398 | 399 | // the bus proxy might not be ready, in this case we will be called later once the bus name appears 400 | if (this._busProxy !== null) { 401 | 402 | // if the actor is set, this means the terminal is opened, so we will handle closing 403 | if (this._windowActor !== null) { 404 | let terminalPosition = this._settings.get_enum(TERMINAL_POSITION_SETTING_KEY); 405 | let targetY = this._windowY; 406 | let targetX = this._windowX; 407 | let animationTime = this._shouldAnimateWindow() ? this._closingAnimationTimeMillis / 1000.0 : 0; 408 | let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; 409 | 410 | switch (terminalPosition) { 411 | case LEFT_EDGE: 412 | targetX = this._windowX - this._windowActor.width; 413 | break; 414 | case RIGHT_EDGE: 415 | targetX = this._windowX + this._windowActor.width; 416 | break; 417 | case BOTTOM_EDGE: 418 | targetY = this._windowY + this._windowActor.height; 419 | break; 420 | case TOP_EDGE: 421 | targetY = this._windowY - this._windowActor.height; 422 | break; 423 | } 424 | 425 | Tweener.addTween(this._windowActor, { 426 | x: targetX, 427 | y: targetY, 428 | time: animationTime, 429 | transition: "easeInExpo", 430 | onUpdate: Lang.bind(this, this._updateClip), 431 | onComplete: Lang.bind(this, function() { 432 | // unregisters the ctrl-alt-tab group 433 | Main.ctrlAltTabManager.removeGroup(this._windowActor); 434 | 435 | // clears the window actor ref since we use it to know the window visibility 436 | this._windowActor = null; 437 | 438 | // requests toggling asynchronously 439 | this._busProxy.ToggleRemote(); 440 | }) 441 | }); 442 | } else { 443 | this._busProxy.ToggleRemote(); 444 | } 445 | } 446 | }, 447 | 448 | _panelScrolled: function(actor, event) { 449 | // checks if toggle on scroll is enabled 450 | if (!this._toggleOnScrollEnabled) { 451 | return; 452 | } 453 | 454 | // only proceed if the terminal is hidden/shown and the scroll occurs 455 | // downwards/upwards 456 | if (this._windowActor === null && event.get_scroll_direction() != Clutter.ScrollDirection.DOWN) { 457 | return; 458 | } 459 | 460 | if (this._windowActor !== null && event.get_scroll_direction() != Clutter.ScrollDirection.UP) { 461 | return; 462 | } 463 | 464 | this._toggle(); 465 | }, 466 | 467 | _updateAnimationProperties: function() { 468 | this._animationEnabled = this._settings.get_boolean(ENABLE_ANIMATION_SETTING_KEY); 469 | this._openingAnimationTimeMillis = this._settings.get_uint(OPENING_ANIMATION_TIME_SETTING_KEY); 470 | this._closingAnimationTimeMillis = this._settings.get_uint(CLOSING_ANIMATION_TIME_SETTING_KEY); 471 | }, 472 | 473 | _updateToggleOnScroll: function() { 474 | this._toggleOnScrollEnabled = this._settings.get_boolean(ENABLE_TOGGLE_ON_SCROLL_SETTING_KEY); 475 | }, 476 | 477 | _updateWindowGeometry: function() { 478 | let screenProxy = global.screen || global.display 479 | let terminalPosition = this._settings.get_enum(TERMINAL_POSITION_SETTING_KEY); 480 | 481 | // computes the window geometry except the height 482 | let panelBox = Main.layoutManager.panelBox; 483 | let sizeSpec = this._settings.get_string(TERMINAL_SIZE_SETTING_KEY); 484 | let panelHeight = Main.layoutManager.panelBox.height; 485 | 486 | let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; 487 | let screen = screenProxy.get_monitor_geometry(screenProxy.get_primary_monitor()); 488 | let bbox = Main.legacyTray ? Main.legacyTray.actor : screen; 489 | let x1 = bbox.x / scaleFactor; 490 | let y1 = bbox.y / scaleFactor; 491 | let screenHeight = bbox.height / scaleFactor; 492 | let screenWidth = bbox.width / scaleFactor; 493 | let x2 = x1 + screenWidth; 494 | let y2 = y1 + screenHeight; 495 | 496 | switch (terminalPosition) { 497 | case LEFT_EDGE: 498 | this._windowX = x1; 499 | this._windowY = panelBox.y == y1 ? y1 + panelBox.height: y1; 500 | this._windowWidth = this._evaluateSizeSpec(sizeSpec, false); 501 | this._windowHeight = panelBox.y == y1 ? screenHeight - panelHeight : screenHeight; 502 | break; 503 | case RIGHT_EDGE: 504 | let width = this._evaluateSizeSpec(sizeSpec, false); 505 | this._windowX = x2 - width; 506 | this._windowY = panelBox.y == y1 ? y1 + panelBox.height: y1; 507 | this._windowWidth = width; 508 | this._windowHeight = panelBox.y == y1 ? screenHeight - panelHeight : screenHeight; 509 | break; 510 | case BOTTOM_EDGE: 511 | let height = this._evaluateSizeSpec(sizeSpec, true); 512 | this._windowX = x1; 513 | this._windowY = panelBox.y + panelBox.height == y2 ? y2 - panelBox.height - height: y2 - height; 514 | this._windowWidth = screenWidth; 515 | this._windowHeight = height; 516 | break; 517 | default: 518 | case TOP_EDGE: 519 | this._windowX = x1; 520 | this._windowY = panelBox.y == y1 ? y1 + panelBox.height: y1; 521 | this._windowWidth = screenWidth; 522 | this._windowHeight = this._evaluateSizeSpec(sizeSpec, true); 523 | break; 524 | } 525 | 526 | // applies the change dynamically if the terminal is already spawn 527 | if (this._busProxy !== null && this._windowHeight !== null) { 528 | this._busProxy.SetGeometryRemote(this._windowX, this._windowY, this._windowWidth, this._windowHeight); 529 | } else if (this._windowActor != null) { 530 | this._windowActor.set_position(this._windowX, this._windowY); 531 | } 532 | 533 | return false; 534 | }, 535 | 536 | _bindShortcut: function() { 537 | if (Main.wm.addKeybinding && Shell.ActionMode) // introduced in 3.16 538 | Main.wm.addKeybinding(REAL_SHORTCUT_SETTING_KEY, this._settings, Meta.KeyBindingFlags.NONE, 539 | Shell.ActionMode.NORMAL | Shell.ActionMode.MESSAGE_TRAY, 540 | Lang.bind(this, this._toggle)); 541 | else if (Main.wm.addKeybinding && Shell.KeyBindingMode) // introduced in 3.7.5 542 | Main.wm.addKeybinding(REAL_SHORTCUT_SETTING_KEY, this._settings, Meta.KeyBindingFlags.NONE, 543 | Shell.KeyBindingMode.NORMAL | Shell.KeyBindingMode.MESSAGE_TRAY, 544 | Lang.bind(this, this._toggle)); 545 | else if (Main.wm.addKeybinding && Main.KeybindingMode) // introduced in 3.7.2 546 | Main.wm.addKeybinding(REAL_SHORTCUT_SETTING_KEY, this._settings, Meta.KeyBindingFlags.NONE, 547 | Main.KeybindingMode.NORMAL | Main.KeybindingMode.MESSAGE_TRAY, 548 | Lang.bind(this, this._toggle)); 549 | else 550 | global.display.add_keybinding(REAL_SHORTCUT_SETTING_KEY, this._settings, Meta.KeyBindingFlags.NONE, 551 | Lang.bind(this, this._toggle)); 552 | 553 | }, 554 | 555 | _unbindShortcut: function() { 556 | if (Main.wm.removeKeybinding) // introduced in 3.7.2 557 | Main.wm.removeKeybinding(REAL_SHORTCUT_SETTING_KEY); 558 | else 559 | global.display.remove_keybinding(REAL_SHORTCUT_SETTING_KEY); 560 | }, 561 | 562 | _windowCreated: function(display, window) { 563 | // filter out the terminal window using its wmclass 564 | if (window.get_wm_class() != TERMINAL_WINDOW_WM_CLASS) { 565 | return; 566 | } 567 | 568 | // gets and decorates the actor 569 | // the opening sequence will be animated when the window will be mapped, in _windowMapped 570 | this._setWindowActor(window.get_compositor_private()); 571 | }, 572 | 573 | _windowMapped: function(wm, actor) { 574 | // filter out the actor using its name 575 | if (actor.get_name() != TERMINAL_WINDOW_ACTOR_NAME) { 576 | return; 577 | } 578 | 579 | // a lambda to request the focus asynchronously 580 | let requestFocusAsync = Lang.bind(this, function() { 581 | if (this._busProxy !== null) { 582 | this._busProxy.FocusRemote(); 583 | } 584 | }); 585 | 586 | // requests the focus asynchronously 587 | requestFocusAsync(); 588 | 589 | // a lambda to complete the opening sequence 590 | let completeOpening = Lang.bind(this, function() { 591 | // registers a ctrl-alt-tab group 592 | Main.ctrlAltTabManager.addGroup(this._windowActor, _("Drop Down Terminal"), 'utilities-terminal-symbolic', 593 | { focusCallback: Lang.bind(this, requestFocusAsync) }); 594 | }); 595 | 596 | // animate the opening sequence if applicable 597 | if (this._shouldAnimateWindow() && this._openingAnimationTimeMillis > 0) { 598 | // FIXME: we should reset those on monitors-changed 599 | // 600 | // to avoid an animation glitch where we could briefly see the window at its target position before the animation starts, 601 | // we initialize the animation in this thread -at actor mapping time- so the window is not yet visible and can be placed out of the screen 602 | // 603 | // Update: starting from mutter 3.7.90, there is no animation at all if the actor is not displayed (out of the screen) at map time. 604 | // This might be a side-effect of the new frame synchronization changes. To work around that until I figure out a better fix, 605 | // schedule the whole animation in the next frame (idle_add) and make the actor transparent until the animation starts 606 | // (to avoid the brief window appearance which is the original issue). 607 | // 608 | // Tonic: I added a workaround by making sure at least one pixel is on the screen. 609 | this._windowActor.opacity = 0; 610 | 611 | Mainloop.idle_add(Lang.bind(this, function() { 612 | let terminalPosition = this._settings.get_enum(TERMINAL_POSITION_SETTING_KEY); 613 | 614 | this._windowActor.opacity = 255; 615 | 616 | // Workaround animation bug by making sure the window is partially on screen. 617 | let workaround = 1; 618 | 619 | switch (terminalPosition) { 620 | case LEFT_EDGE: 621 | this._windowActor.set_position(this._windowX - this._windowActor.width + workaround, this._windowY); 622 | break; 623 | case RIGHT_EDGE: 624 | this._windowActor.set_position(this._windowX + this._windowActor.width - workaround, this._windowY); 625 | break; 626 | case BOTTOM_EDGE: 627 | this._windowActor.set_position(this._windowX, this._windowY + this._windowActor.height - workaround); 628 | break; 629 | default: 630 | case TOP_EDGE: 631 | this._windowActor.set_position(this._windowX, this._windowY - this._windowActor.height + workaround); 632 | break; 633 | } 634 | 635 | Tweener.addTween(this._windowActor, { 636 | y: this._windowY, 637 | x: this._windowX, 638 | onUpdate: Lang.bind(this, this._updateClip), 639 | scale_y: 1.0, 640 | time: this._openingAnimationTimeMillis / 1000.0, 641 | transition: "easeOutExpo", 642 | onComplete: completeOpening 643 | }); 644 | })); 645 | } else { 646 | completeOpening(); 647 | } 648 | }, 649 | 650 | _forkChild: function() { 651 | // resets the child finishing flags 652 | this._quitingChild = false; 653 | this._killingChild = false; 654 | 655 | // finds the forking arguments 656 | let args = ["gjs", GLib.build_filenamev([Me.path, "terminal.js"]), Me.path]; 657 | 658 | // forks the process 659 | debug("forking '" + args.join(" ") + "'"); 660 | 661 | let success, pid; 662 | 663 | try { 664 | [success, pid] = GLib.spawn_async(null, args, this._getCommandEnv(), 665 | GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD, 666 | null); 667 | } catch (err) { 668 | // beautify the most common error message 669 | if (err.code == GLib.SpawnError.G_SPAWN_ERROR_NOENT) { 670 | err.message = _("gjs not found in PATH"); 671 | } 672 | 673 | debug("failed to fork the terminal script (" + err.code + ", " + err.message + ")"); 674 | 675 | // The exception from gjs contains an error string like: 676 | // Error invoking GLib.spawn_command_line_async: Failed to 677 | // execute child process "foo" (No such file or directory) 678 | // We are only interested in the part in the parentheses. (And 679 | // we can't pattern match the text, since it gets localized.) 680 | err.message = err.message.replace(/.*\((.+)\)/, '$1'); 681 | 682 | Main.notifyError(_("Drop Down Terminal failed to start"), _("Cause: %s").format(err.message)); 683 | 684 | throw err; 685 | } 686 | 687 | // keep the child pid around 688 | this._childPid = pid; 689 | 690 | // adds a watch to know when the child process ends 691 | GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, Lang.bind(this, this._childExited), null); 692 | }, 693 | 694 | _killChild: function() { 695 | if (this._childPid !== null) { 696 | // we are killing the child ourself, so set the flag telling this is intended 697 | this._killingChild = true; 698 | 699 | let args = ["kill", "-9", "" + this._childPid]; 700 | 701 | try { 702 | GLib.spawn_sync(null, args, null, GLib.SpawnFlags.SEARCH_PATH, null, null); 703 | } catch (e) { 704 | logError(e, "Failed to kill process " + this._childPid); 705 | Main.notifyError(_("Could not kill the Drop Down Terminal"), 706 | _("The terminal process could not be terminated.") + "\n\n" + _("You can activate the debug mode to nail down the issue")); 707 | } 708 | } 709 | }, 710 | 711 | _childExited: function(pid, status) { 712 | // closes the process 713 | GLib.spawn_close_pid(pid); 714 | 715 | // check the exit status 716 | if (!this._quitingChild && !this._killingChild) { 717 | debug("terminal exited abruptly with status " + status); 718 | 719 | Main.notifyError(_("Drop Down Terminal ended abruptly"), _("You can activate the debug mode to nail down the issue")); 720 | } 721 | 722 | // forgets the child and the bus proxy 723 | this._forgetChild(); 724 | }, 725 | 726 | _busNameAppeared: function(connection, name, nameOwner) { 727 | // creates a dbus proxy on the interface exported by the child process 728 | let DropDownTerminalDBusProxy = Gio.DBusProxy.makeProxyWrapper(DropDownTerminalIface); 729 | 730 | this._busProxy = new DropDownTerminalDBusProxy(Gio.DBus.session, "org.zzrough.GsExtensions.DropDownTerminal", "/org/zzrough/GsExtensions/DropDownTerminal"); 731 | 732 | // connects to the Failure signal to report errors 733 | this._busProxy.connectSignal("Failure", Lang.bind(this, function(proxy, sender, [name, cause]) { 734 | debug("failure reported by the terminal: " + cause); 735 | 736 | if (name == "ForkUserShellFailed") { 737 | Main.notifyError(_("Drop Down Terminal failed to start"), 738 | _("The user shell could not be spawn in the terminal.") + "\n\n" + _("You can activate the debug mode to nail down the issue")); 739 | } else { 740 | Main.notifyError(_("An error occured in the Drop Down Terminal"), 741 | cause + "\n\n" + _("You can activate the debug mode to nail down the issue")); 742 | } 743 | })); 744 | 745 | // applies the geometry if applicable 746 | if (this._windowHeight !== null) { 747 | this._busProxy.SetGeometryRemote(this._windowX, this._windowY, this._windowWidth, this._windowHeight); 748 | } 749 | 750 | // initial toggling if explicitely asked to, since we we can also be called on a shell restart 751 | // (the shell reexec itself thus not letting the extensions a chance to properly shut down) 752 | if (this._toggleOnBusNameAppearance) { 753 | this._toggle(); 754 | this._toggleOnBusAppearance = false; 755 | } 756 | }, 757 | 758 | _busNameVanished: function(connection, name) { 759 | // forgets the bus proxy and the child entirely since they will be automatically picked 760 | // up again once available, even if that means spawning the child again if necessary 761 | this._forgetChild(); 762 | }, 763 | 764 | _forgetChild: function() { 765 | // destroys the dbus proxy aggressively 766 | delete this._busProxy; 767 | this._busProxy = null; 768 | 769 | // forgets about the child pid too, it will be find out again at the next toggle if the bus is 770 | // activated meanwhile 771 | this._childPid = null; 772 | }, 773 | 774 | _setWindowActor: function(actor) { 775 | // adds a gray border on south of the actor to mimick the shell borders 776 | actor.clear_effects(); 777 | actor.add_effect(new SouthBorderEffect()); 778 | 779 | // sets a distinctive name for the actor 780 | actor.set_name(TERMINAL_WINDOW_ACTOR_NAME); 781 | 782 | // keeps the ref 783 | this._windowActor = actor; 784 | }, 785 | 786 | _evaluateSizeSpec: function(heightSpec, vertical) { 787 | // updates the height from the height spec, so it's picked 788 | let match = heightSpec.trim().match(/^([1-9]\d*)\s*(px|%)$/i); 789 | 790 | if (match === null) { 791 | return null; 792 | } 793 | 794 | let value = parseInt(match[1]); 795 | let type = match[2]; 796 | 797 | if (type.toLowerCase() == "px") { 798 | return (value > 0) ? value : null; 799 | } else { 800 | if (value <= 0 || value > 100) { 801 | return null; 802 | } 803 | 804 | let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; 805 | 806 | if (vertical) { 807 | let monitorHeight = Main.layoutManager.primaryMonitor.height / scaleFactor; 808 | let panelHeight = Main.layoutManager.panelBox.height; 809 | return parseInt((monitorHeight - panelHeight) * value / 100.0); 810 | } else { 811 | let monitorWidth = Main.layoutManager.primaryMonitor.width / scaleFactor; 812 | return parseInt(monitorWidth * value / 100.0); 813 | } 814 | } 815 | }, 816 | 817 | _updateClip: function() { 818 | let monitor = Main.layoutManager.primaryMonitor; 819 | let a = this._windowActor.allocation; 820 | let clip = new Clutter.ActorBox({ 821 | x1: Math.max(monitor.x, a.x1), 822 | y1: Math.max(monitor.y, a.y1), 823 | x2: Math.min(monitor.x + monitor.width, a.x2), 824 | y2: Math.min(monitor.y + monitor.height, a.y2) 825 | }); 826 | 827 | clip.x1 -= this._windowActor.x; 828 | clip.x2 -= this._windowActor.x; 829 | clip.y1 -= this._windowActor.y; 830 | clip.y2 -= this._windowActor.y; 831 | 832 | this._windowActor.set_clip(clip.x1, clip.y1, clip.x2 - clip.x1, clip.y2 - clip.y1); 833 | }, 834 | 835 | _shouldAnimateWindow: function() { 836 | if (!this._animationEnabled || !Main.wm._shouldAnimate()) { 837 | return false; 838 | } 839 | 840 | for (let ext in ExtensionUtils.extensions) { 841 | if (ANIMATION_CONFLICT_EXTENSION_UUIDS.indexOf(ext.uuid) >= 0 && ext.state == ExtensionSystem.ExtensionState.ENABLED) { 842 | return false; 843 | } 844 | } 845 | 846 | return true; 847 | }, 848 | 849 | _getCommandEnv: function() { 850 | // builds the environment 851 | let env = {}; 852 | 853 | GLib.listenv().forEach(function(name) { 854 | env[name] = GLib.getenv(name); 855 | }); 856 | 857 | env["GJS_PATH"] = Me.path; 858 | 859 | // gets an array of key=value pairs 860 | let envArray = []; 861 | 862 | for (let key in env) { 863 | envArray.push(key + "=" + (env[key] ? env[key] : "")); 864 | } 865 | 866 | return envArray; 867 | }, 868 | 869 | _handleFirstStart: function() { 870 | // checks the first start key 871 | if (!this._settings.get_boolean(FIRST_START_SETTING_KEY)) { 872 | return; 873 | } 874 | 875 | // opens the preferences up 876 | try { 877 | GLib.spawn_command_line_async("gnome-shell-extension-prefs " + Me.uuid); 878 | } catch (err) { 879 | logError(err, "Could not start gnome-shell-extension-prefs"); 880 | Main.notifyError(_("Error while opening the preferences"), _("gnome-shell-extension-prefs could not be started, check your session log for more details")); 881 | } 882 | 883 | // updates the first start key 884 | this._settings.set_boolean(FIRST_START_SETTING_KEY, false); 885 | }, 886 | 887 | _checkDependencies: function() { 888 | try { 889 | imports.gi.Vte; 890 | } catch (e) { 891 | // creates and opens the dialog after 1 second 892 | Mainloop.timeout_add_seconds(1, function() { 893 | new MissingVteDialog().open(); 894 | return false; 895 | }); 896 | 897 | logError(e, "Vte could not be imported"); 898 | throw e; 899 | } 900 | } 901 | }); 902 | 903 | 904 | // extension init hook 905 | function init() { 906 | return new DropDownTerminalExtension(); 907 | } 908 | 909 | -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/gschemas.compiled: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anarchodin/gs-extensions-drop-down-terminal/ec6d63458a3343ec3d92555bd6a533eca7b9b8f3/drop-down-terminal@gs-extensions.zzrough.org/gschemas.compiled -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/gtk-3-14.css: -------------------------------------------------------------------------------- 1 | /* Container fixes */ 2 | GtkWindow { 3 | background-color: rgba(0,0,0,0.0); 4 | } 5 | 6 | GtkWindow * { 7 | box-shadow: none; /* removes the container's faint shadow */ 8 | } 9 | 10 | .scrollbar.trough { 11 | background-color: #000; 12 | } 13 | 14 | /* Mini Reset for best cross-theme compatibility */ 15 | .menu, 16 | .menu .menuitem { 17 | padding: 0; 18 | margin: 0; 19 | border-color: #a5a5a5; 20 | border-style: solid; 21 | background-color: #000; 22 | } 23 | 24 | .menu { 25 | color: white; 26 | border-width: 2px; 27 | border-radius: 8px; 28 | padding: 1.4em 0; 29 | } 30 | 31 | .menu .menuitem { 32 | border-width: 0 2px; 33 | font: Cantarell 11; 34 | padding: 0.5em 4.75em 0.5em 0.78em; 35 | } 36 | 37 | .menu .menuitem:active, 38 | .menu .menuitem:hover { 39 | background-color: #4c4c4c; 40 | } 41 | -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/gtk-3-16.css: -------------------------------------------------------------------------------- 1 | /* Container fixes */ 2 | GtkWindow { 3 | background-color: rgba(0,0,0,0.0); 4 | } 5 | 6 | GtkWindow * { 7 | box-shadow: none; /* removes the container's faint shadow */ 8 | } 9 | 10 | .scrollbar.trough { 11 | background-color: #000; 12 | } 13 | 14 | /* Mini Reset for best cross-theme compatibility */ 15 | .menu, 16 | .menu .menuitem { 17 | padding: 0; 18 | margin: 0; 19 | border-color: #a5a5a5; 20 | border-style: solid; 21 | border-width: 0; 22 | background-color: #383d3d; 23 | } 24 | 25 | .menu { 26 | color: white; 27 | border-radius: 3px; 28 | padding: 1.4em 0; 29 | } 30 | 31 | .menu .menuitem { 32 | font: Cantarell 11; 33 | padding: 0.5em 4.75em 0.5em 0.78em; 34 | } 35 | 36 | .menu .menuitem:active, 37 | .menu .menuitem:hover { 38 | background-color: #4a4f4f; 39 | } 40 | -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/gtk.css: -------------------------------------------------------------------------------- 1 | /* Container fixes */ 2 | window, 3 | GtkWindow { 4 | background-color: rgba(0,0,0,0.0); 5 | } 6 | 7 | window *, 8 | GtkWindow * { 9 | box-shadow: none; /* removes the container's faint shadow */ 10 | } 11 | 12 | /* Do not display the undershoot */ 13 | scrolledwindow undershoot.top, 14 | scrolledwindow undershoot.right, 15 | scrolledwindow undershoot.bottom, 16 | scrolledwindow undershoot.left { 17 | background-image: none; 18 | } 19 | 20 | /* Mini Reset for best cross-theme compatibility */ 21 | .menu, 22 | .menu .menuitem { 23 | padding: 0; 24 | margin: 0; 25 | border-color: #a5a5a5; 26 | border-style: solid; 27 | border-width: 0; 28 | background-color: #383d3d; 29 | } 30 | 31 | .menu { 32 | color: white; 33 | border-radius: 3px; 34 | padding: 1.4em 0; 35 | } 36 | 37 | .menu .menuitem { 38 | font-family: "Cantarell"; 39 | font-size: 11px; 40 | padding: 0.5em 4.75em 0.5em 0.78em; 41 | } 42 | 43 | .menu .menuitem:active, 44 | .menu .menuitem:hover { 45 | background-color: #4a4f4f; 46 | } 47 | -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "drop-down-terminal", 3 | "uuid": "drop-down-terminal@gs-extensions.zzrough.org", 4 | "name": "Drop Down Terminal", 5 | "description": "Drop down terminal toggled by a keystroke (the key above tab by default) for advanced users.", 6 | "shell-version": ["3.14", "3.16", "3.18", "3.19.91", "3.19.92", "3.20", "3.21.1", "3.21.2", "3.21.3", "3.21.4", "3.22"], 7 | "url": "https://github.com/zzrough/gs-extensions-drop-down-terminal" 8 | } 9 | 10 | -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/org.zzrough.gs-extensions.drop-down-terminal.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | true 12 | An internal key that must not be changed manually 13 | 14 | Whether this is the first start of the extension or not. It is used to display a welcome message 15 | and/or open preferences. 16 | 17 | 18 | 19 | 20 | true 21 | Terminal bell 22 | 23 | Whether to enable audible confirmation in terminal. 24 | 25 | 26 | 27 | 28 | true 29 | Enable the animation 30 | 31 | Whether to enable or disable the opening/closing animation. 32 | 33 | 34 | 35 | 36 | 250 37 | The opening animation time in ms 38 | 39 | The opening animation time in milliseconds or zero for no opening animation. 40 | 41 | 42 | 43 | 44 | 250 45 | The closing animation time in ms 46 | 47 | The closing animation time in milliseconds or zero for no closing animation. 48 | 49 | 50 | 51 | 52 | true 53 | Enable the terminal window transparency 54 | 55 | Whether to enable terminal transparency (true) or keep the terminal opaque (false). 56 | 57 | 58 | 59 | 60 | 94 61 | The transparency level 62 | 63 | The transparency level between 0 (fully transparent) and 100 (fully opaque), which is 64 | only used if 'transparent-terminal' is set. 65 | 66 | 67 | 68 | 69 | 70 | The terminal foreground (default text) color 71 | 72 | The terminal foreground color for normal text as hexadecimal value in the form "#rgb", 73 | "#rrggbb", "#rrrgggbbb" or "#rrrrggggbbbb". 74 | 75 | 76 | 77 | 78 | 79 | The terminal background color 80 | 81 | The terminal background color as hexadecimal value in the form "#rgb", "#rrggbb", 82 | "#rrrgggbbb" or "#rrrrggggbbbb". 83 | 84 | 85 | 86 | 87 | false 88 | Enable the terminal scrollbar 89 | 90 | Whether the terminal scrollbar is visible (true) or hidden (false). 91 | 92 | 93 | 94 | 95 | 96 | The terminal window position 97 | 98 | The terminal window size in pixels (400px for instance) or in percentage (30% for instance). The size value is used for the height or width if the position is top/bottom, or left/right. 99 | 100 | 101 | 102 | 103 | 'Top' 104 | The terminal window position 105 | 106 | The edge of the screen to which the terminal window is aligned. 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | The shortcut type 'default' or 'other' 117 | 118 | The type of shortcut used to open and close the terminal. If 'other' is set, the definition must be set 119 | in the 'other-shortcut' key. 120 | 121 | 122 | 123 | 124 | 125 | The manual shortcut used to toggle the terminal 126 | 127 | The shortcut used to open and close the terminal if shortcut-type is 'other'. 128 | 129 | 130 | 131 | 132 | false 133 | Enable toggling by scrolling the top bar 134 | 135 | Whether to enable toggling by scrolling on the top bar of the primary screen. 136 | 137 | 138 | 139 | 140 | 141 | An internal key that must not be changed manually 142 | 143 | 'Above_Tab' is displayed as 'Disabled' in a GtkCellRendererAccel and that really looks bad. 144 | This is why there is this special key which is the final, real shortcut used, and which is not displayed 145 | in the preferences UI. 146 | 147 | 148 | 149 | 150 | false 151 | Whether to run a custom command or the default 152 | 153 | Whether to use the custom command set in the 'custom-command' key or not (the user shell is used instead). 154 | 155 | 156 | 157 | 158 | '' 159 | The custom command to launch 160 | 161 | The custom command to launch in the terminal, only used if the 'run-custom-command' key value is 'true'. 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/prefs.gtkbuilder: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Top 15 | 0 16 | 17 | 18 | Left 19 | 1 20 | 21 | 22 | Right 23 | 2 24 | 25 | 26 | Bottom 27 | 3 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 100 41 | 94 42 | 1 43 | 10 44 | 45 | 46 | True 47 | True 48 | 49 | 50 | True 51 | False 52 | 12 53 | 12 54 | 12 55 | 12 56 | vertical 57 | 6 58 | 59 | 60 | True 61 | False 62 | 0 63 | none 64 | 65 | 66 | True 67 | False 68 | 6 69 | 12 70 | 71 | 72 | True 73 | False 74 | 6 75 | 76 | 77 | Animation 78 | True 79 | True 80 | False 81 | True 82 | 0 83 | True 84 | 85 | 86 | 0 87 | 0 88 | 3 89 | 90 | 91 | 92 | 93 | 520 94 | True 95 | False 96 | <small>Transparency support is only included on certain distributions like Fedora as the support has been removed on the upstream VTE project.</small> 97 | True 98 | True 99 | 0 100 | 101 | 102 | 1 103 | 3 104 | 2 105 | 106 | 107 | 108 | 109 | Scrollbar 110 | True 111 | True 112 | False 113 | 6 114 | 0 115 | True 116 | 117 | 118 | 0 119 | 4 120 | 3 121 | 122 | 123 | 124 | 125 | True 126 | False 127 | 128 | 129 | 0 130 | 3 131 | 132 | 133 | 134 | 135 | Transparency 136 | True 137 | True 138 | False 139 | 6 140 | True 141 | 0 142 | True 143 | 144 | 145 | 0 146 | 1 147 | 3 148 | 149 | 150 | 151 | 152 | True 153 | False 154 | 155 | 156 | 0 157 | 2 158 | 159 | 160 | 161 | 162 | True 163 | True 164 | transparency-level-adjustment 165 | True 166 | 94 167 | 168 | 169 | 1 170 | 2 171 | 172 | 173 | 174 | 175 | True 176 | False 177 | Position 178 | 179 | 180 | 0 181 | 5 182 | 183 | 184 | 185 | 186 | True 187 | False 188 | start 189 | 6 190 | 2 191 | Height 192 | 0 193 | 194 | 195 | 0 196 | 6 197 | 198 | 199 | 200 | 201 | True 202 | True 203 | start 204 | 6 205 | 206 | 20 207 | False 208 | False 209 | 210 | 211 | 1 212 | 6 213 | 214 | 215 | 216 | 217 | True 218 | True 219 | True 220 | Reset the field to its default value 221 | start 222 | 6 223 | True 224 | 225 | 226 | True 227 | False 228 | gtk-revert-to-saved 229 | 230 | 231 | 232 | 233 | 2 234 | 6 235 | 236 | 237 | 238 | 239 | True 240 | False 241 | <small>The size in pixels (400px for instance) or in percentage (30% for instance)</small> 242 | True 243 | start 244 | 0 245 | 246 | 247 | 1 248 | 7 249 | 2 250 | 251 | 252 | 253 | 254 | True 255 | False 256 | 257 | 258 | 0 259 | 7 260 | 261 | 262 | 263 | 264 | True 265 | False 266 | liststore1 267 | 0 268 | 269 | 270 | 271 | 0 272 | 273 | 274 | 275 | 276 | 1 277 | 5 278 | 279 | 280 | 281 | 282 | True 283 | False 284 | 285 | 286 | 2 287 | 2 288 | 289 | 290 | 291 | 292 | True 293 | False 294 | 295 | 296 | 2 297 | 5 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | True 307 | False 308 | <b>Appearance</b> 309 | True 310 | 311 | 312 | 313 | 314 | False 315 | True 316 | 6 317 | 0 318 | 319 | 320 | 321 | 322 | True 323 | False 324 | 0 325 | none 326 | 327 | 328 | True 329 | False 330 | 6 331 | 12 332 | 333 | 334 | True 335 | False 336 | 12 337 | 6 338 | 339 | 340 | Custom shortcut 341 | True 342 | True 343 | False 344 | 0 345 | True 346 | default-shortcut-radiobutton 347 | 348 | 349 | 0 350 | 1 351 | 352 | 353 | 354 | 355 | True 356 | False 357 | True 358 | True 359 | 360 | 361 | True 362 | True 363 | other-shortcut-liststore 364 | False 365 | 0 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 1 374 | 1 375 | 376 | 377 | 378 | 379 | Toggle on scroll 380 | True 381 | True 382 | False 383 | 0 384 | True 385 | 386 | 387 | 0 388 | 2 389 | 3 390 | 391 | 392 | 393 | 394 | True 395 | False 396 | True 397 | 398 | 399 | 2 400 | 1 401 | 402 | 403 | 404 | 405 | Default shortcut 406 | True 407 | True 408 | False 409 | start 410 | 2 411 | True 412 | 0 413 | True 414 | True 415 | 416 | 417 | 0 418 | 0 419 | 420 | 421 | 422 | 423 | True 424 | False 425 | start 426 | True 427 | vertical 428 | 429 | 430 | True 431 | False 432 | Key above "Tab" 433 | 0 434 | 435 | 436 | False 437 | True 438 | 3 439 | 0 440 | 441 | 442 | 443 | 444 | True 445 | True 446 | True 447 | <small>This key is the one above "Tab" on many keyboard layouts, like good old <a href="https://en.wikipedia.org/wiki/Quake_%28video_game%29" title="A bit of history">Quake</a>. 448 | Tip: Alt + this key allows to cycle between the windows of an application.</small> 449 | True 450 | 0 451 | 452 | 453 | True 454 | False 455 | 1 456 | 457 | 458 | 459 | 460 | 1 461 | 0 462 | 2 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | True 472 | False 473 | <b>Activation</b> 474 | True 475 | 476 | 477 | 478 | 479 | False 480 | True 481 | 6 482 | 1 483 | 484 | 485 | 486 | 487 | 488 | 489 | True 490 | False 491 | General 492 | 493 | 494 | False 495 | 496 | 497 | 498 | 499 | True 500 | False 501 | 12 502 | 12 503 | 12 504 | 12 505 | vertical 506 | 6 507 | 508 | 509 | True 510 | False 511 | 0 512 | none 513 | 514 | 515 | True 516 | False 517 | 6 518 | 12 519 | 520 | 521 | True 522 | False 523 | 6 524 | 6 525 | 526 | 527 | True 528 | False 529 | <small>Please do not ask for full color support: I want it as much as you do but <b>it can't be done yet</b>. There are shortcomings with the JS bindings that make color support limited.</small> 530 | True 531 | True 532 | 0 533 | 534 | 535 | 1 536 | 2 537 | 3 538 | 539 | 540 | 541 | 542 | True 543 | False 544 | 545 | 546 | 0 547 | 2 548 | 549 | 550 | 551 | 552 | True 553 | False 554 | Text color 555 | 0 556 | 557 | 558 | 0 559 | 0 560 | 561 | 562 | 563 | 564 | True 565 | True 566 | True 567 | start 568 | center 569 | 570 | 571 | 1 572 | 0 573 | 574 | 575 | 576 | 577 | True 578 | True 579 | True 580 | Reset the field to its default value 581 | start 582 | 0 583 | 584 | 585 | True 586 | False 587 | gtk-revert-to-saved 588 | 589 | 590 | 591 | 592 | 2 593 | 0 594 | 595 | 596 | 597 | 598 | True 599 | False 600 | Background color 601 | 0 602 | 603 | 604 | 0 605 | 1 606 | 607 | 608 | 609 | 610 | True 611 | True 612 | True 613 | start 614 | center 615 | 616 | 617 | 1 618 | 1 619 | 620 | 621 | 622 | 623 | True 624 | True 625 | True 626 | Reset the field to its default value 627 | start 628 | 0 629 | 630 | 631 | True 632 | False 633 | gtk-revert-to-saved 634 | 635 | 636 | 637 | 638 | 2 639 | 1 640 | 641 | 642 | 643 | 644 | True 645 | False 646 | True 647 | 648 | 649 | 3 650 | 0 651 | 652 | 653 | 654 | 655 | True 656 | False 657 | True 658 | 659 | 660 | 3 661 | 1 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | True 671 | False 672 | <b>Colors</b> 673 | True 674 | 675 | 676 | 677 | 678 | False 679 | True 680 | 6 681 | 0 682 | 683 | 684 | 685 | 686 | True 687 | False 688 | 0 689 | none 690 | 691 | 692 | True 693 | False 694 | 6 695 | 12 696 | 697 | 698 | True 699 | False 700 | True 701 | 6 702 | 6 703 | 704 | 705 | Run a custom command instead of my shell 706 | True 707 | True 708 | False 709 | 0 710 | True 711 | 712 | 713 | 0 714 | 0 715 | 2 716 | 717 | 718 | 719 | 720 | True 721 | False 722 | True 723 | 6 724 | 725 | 726 | True 727 | False 728 | 24 729 | 730 | 731 | True 732 | False 733 | Custom command 734 | 0 735 | 736 | 737 | 738 | 739 | False 740 | True 741 | 0 742 | 743 | 744 | 745 | 746 | True 747 | True 748 | True 749 | 750 | 20 751 | False 752 | False 753 | 754 | 755 | True 756 | True 757 | 1 758 | 759 | 760 | 761 | 762 | 0 763 | 1 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | True 776 | False 777 | <b>Command</b> 778 | True 779 | 780 | 781 | 782 | 783 | False 784 | True 785 | 6 786 | 1 787 | 788 | 789 | 790 | 791 | True 792 | False 793 | 0 794 | none 795 | 796 | 797 | True 798 | False 799 | 12 800 | 801 | 802 | Terminal bell 803 | True 804 | True 805 | False 806 | 0 807 | True 808 | 809 | 810 | 811 | 812 | 813 | 814 | True 815 | False 816 | <b>Misc</b> 817 | True 818 | end 819 | 0.029999999999999999 820 | 821 | 822 | 823 | 824 | False 825 | True 826 | 2 827 | 828 | 829 | 830 | 831 | 1 832 | 833 | 834 | 835 | 836 | True 837 | False 838 | Terminal 839 | 840 | 841 | 1 842 | False 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/prefs.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 Stéphane Démurget 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | // Author: Stéphane Démurget 17 | 18 | const Lang = imports.lang; 19 | const Gettext = imports.gettext.domain('drop-down-terminal'); 20 | 21 | const GLib = imports.gi.GLib; 22 | const GObject = imports.gi.GObject; 23 | const Gio = imports.gi.Gio; 24 | const Gtk = imports.gi.Gtk; 25 | 26 | const _ = Gettext.gettext; 27 | const Me = imports.misc.extensionUtils.getCurrentExtension(); 28 | const Convenience = Me.imports.convenience; 29 | 30 | 31 | // setting keys 32 | const ENABLE_ANIMATION_SETTING_KEY = "enable-animation"; 33 | const TRANSPARENT_TERMINAL_SETTING_KEY = "transparent-terminal"; 34 | const SCROLLBAR_VISIBLE_SETTING_KEY = "scrollbar-visible"; 35 | const TERMINAL_SIZE_SETTING_KEY = "terminal-size"; 36 | const TERMINAL_POSITION_SETTING_KEY = "terminal-position"; 37 | const TRANSPARENCY_LEVEL_SETTING_KEY = "transparency-level"; 38 | const SHORTCUT_TYPE_SETTING_KEY = "shortcut-type"; 39 | const OTHER_SHORTCUT_SETTING_KEY = "other-shortcut"; 40 | const REAL_SHORTCUT_SETTING_KEY = "real-shortcut"; 41 | const ENABLE_TOGGLE_ON_SCROLL_SETTING_KEY = "enable-toggle-on-scroll"; 42 | const FOREGROUND_COLOR_SETTING_KEY = "foreground-color"; 43 | const BACKGROUND_COLOR_SETTING_KEY = "background-color"; 44 | const RUN_CUSTOM_COMMAND_SETTING_KEY = "run-custom-command"; 45 | const CUSTOM_COMMAND_SETTING_KEY = "custom-command"; 46 | const ENABLE_AUDIBLE_BELL_KEY = "enable-audible-bell"; 47 | 48 | // shortcut tree view columns 49 | const SHORTCUT_COLUMN_KEY = 0; 50 | const SHORTCUT_COLUMN_MODS = 1; 51 | 52 | 53 | // settings widget 54 | const DropDownTerminalSettingsWidget = new GObject.Class({ 55 | Name: 'DropDownTerminal.Prefs.DropDownTerminalSettingsWidget', 56 | GTypeName: 'DropDownTerminalSettingsWidget', 57 | Extends: Gtk.Box, 58 | 59 | _init: function(params) { 60 | this.parent(params); 61 | 62 | this.orientation = Gtk.Orientation.VERTICAL; 63 | this.spacign = 0; 64 | 65 | // creates the settings 66 | this._settings = Convenience.getSettings(Me.path, Me.metadata.id); 67 | 68 | // creates the ui builder and add the main resource file 69 | let uiFilePath = Me.path + "/prefs.gtkbuilder"; 70 | let builder = new Gtk.Builder(); 71 | 72 | if (builder.add_from_file(uiFilePath) == 0) { 73 | log("could not load the ui file: %s".format(uiFilePath)); 74 | 75 | let label = new Gtk.Label({ 76 | label: _("Could not load the preferences UI file"), 77 | vexpand: true 78 | }); 79 | 80 | this.pack_start(label, true, true, 0); 81 | } else { 82 | // gets the interesting builder objects 83 | let mainNotebook = builder.get_object("main-notebook"); 84 | let enableAnimationCheckButton = builder.get_object("enable-animation-checkbutton"); 85 | let transparentTerminalCheckButton = builder.get_object("transparent-terminal-checkbutton"); 86 | let scrollbarVisibleCheckButton = builder.get_object("scrollbar-visible-checkbutton"); 87 | let terminalSizeEntry = builder.get_object("terminal-size-entry"); 88 | let terminalSizeResetButton = builder.get_object("terminal-size-reset-button"); 89 | let transparencyLevelSpinButton = builder.get_object("transparency-level-spinbutton"); 90 | let defaultShortcutRadioButton = builder.get_object("default-shortcut-radiobutton"); 91 | let enableToggleOnScrollCheckButton = builder.get_object("enable-toggle-on-scroll-checkbutton"); 92 | let foregroundColorResetButton = builder.get_object("foreground-color-reset-button"); 93 | let backgroundColorResetButton = builder.get_object("background-color-reset-button"); 94 | let positionComboBox = builder.get_object("position-combobox"); 95 | let enableAudibleBellCheckButton = builder.get_object("enable-audible-bell-checkbutton"); 96 | this._foregroundColorButton = builder.get_object("foreground-color-button"); 97 | this._backgroundColorButton = builder.get_object("background-color-button"); 98 | this._otherShortcutRadioButton = builder.get_object("other-shortcut-radiobutton"); 99 | this._otherShortcutTreeView = builder.get_object("other-shortcut-treeview"); 100 | this._otherShortcutListStore = builder.get_object("other-shortcut-liststore"); 101 | this._runCustomCommandCheckButton = builder.get_object("run-custom-command-checkbutton"); 102 | this._customCommandBox = builder.get_object("custom-command-box"); 103 | this._customCommandEntry = builder.get_object("custom-command-entry"); 104 | 105 | // packs the main box 106 | this.pack_start(mainNotebook, true, true, 0); 107 | 108 | // gives a hint on invalid window height input (does not prevent from writing a wrong value) 109 | terminalSizeEntry.connect("changed", Lang.bind(this, function() { 110 | let match = terminalSizeEntry.get_text().trim().match(/^([1-9]\d*)\s*(px|%)$/i); 111 | let valid = (match !== null) 112 | 113 | if (valid) { 114 | let value = parseInt(match[1]); 115 | let type = match[2]; 116 | 117 | valid = (type.toLowerCase() == "px") ? (value > 0) 118 | : (value > 0 && value <= 100); 119 | } 120 | 121 | terminalSizeEntry["secondary-icon-name"] = valid ? null : "dialog-warning-symbolic"; 122 | terminalSizeEntry["secondary-icon-tooltip-text"] = valid ? null : _("Invalid syntax or range"); 123 | })); 124 | 125 | // configure the tree view column and creates the unique row of the model 126 | this._configureOtherShortcutTreeView(this._otherShortcutTreeView); 127 | this._otherShortcutRowIter = this._otherShortcutListStore.append(); 128 | 129 | // binds the animation enablement setting 130 | this._settings.bind(ENABLE_ANIMATION_SETTING_KEY, enableAnimationCheckButton, "active", Gio.SettingsBindFlags.DEFAULT); 131 | 132 | // binds the terminal transparency setting 133 | this._settings.bind(TRANSPARENT_TERMINAL_SETTING_KEY, transparentTerminalCheckButton, "active", Gio.SettingsBindFlags.DEFAULT); 134 | 135 | // binds the scrollbar visibility setting 136 | this._settings.bind(SCROLLBAR_VISIBLE_SETTING_KEY, scrollbarVisibleCheckButton, "active", Gio.SettingsBindFlags.DEFAULT); 137 | 138 | // binds the terminal height setting 139 | this._settings.bind(TERMINAL_SIZE_SETTING_KEY, terminalSizeEntry, "text", Gio.SettingsBindFlags.DEFAULT); 140 | terminalSizeResetButton.connect("clicked", Lang.bind(this, function() { this._settings.reset(TERMINAL_SIZE_SETTING_KEY); })); 141 | 142 | // binds the custom shortcut setting 143 | this._settings.connect("changed::" + OTHER_SHORTCUT_SETTING_KEY, Lang.bind(this, this._otherShortcutSettingChanged)); 144 | this._otherShortcutSettingChanged(); 145 | 146 | // binds the shortcut type (too bad bind_with_mapping is not introspectable) 147 | this._settings.connect("changed::" + SHORTCUT_TYPE_SETTING_KEY, Lang.bind(this, this._shortcutTypeSettingChanged)); 148 | this._shortcutTypeSettingChanged(); 149 | 150 | this._otherShortcutRadioButton.connect("notify::active", Lang.bind(this, function() { 151 | this._settings.set_string(SHORTCUT_TYPE_SETTING_KEY, this._otherShortcutRadioButton["active"] ? "other" : "default"); 152 | this._shortcutTypeSettingChanged(); 153 | })); 154 | 155 | // binds the toggle on scroll setting 156 | this._settings.bind(ENABLE_TOGGLE_ON_SCROLL_SETTING_KEY, enableToggleOnScrollCheckButton, "active", Gio.SettingsBindFlags.DEFAULT); 157 | 158 | // binds the custom command settings 159 | this._settings.bind(RUN_CUSTOM_COMMAND_SETTING_KEY, this._runCustomCommandCheckButton, "active", Gio.SettingsBindFlags.DEFAULT); 160 | this._settings.bind(CUSTOM_COMMAND_SETTING_KEY, this._customCommandEntry, "text", Gio.SettingsBindFlags.DEFAULT); 161 | 162 | this._runCustomCommandCheckButton.connect("notify::active", Lang.bind(this, this._runCustomCommandCheckButtonToggled)); 163 | this._customCommandEntry.connect("changed", Lang.bind(this, this._checkCustomCommandEntry)); 164 | 165 | this._runCustomCommandCheckButtonToggled(); 166 | this._checkCustomCommandEntry(); 167 | 168 | // binds the terminal bett setting 169 | this._settings.bind(ENABLE_AUDIBLE_BELL_KEY, enableAudibleBellCheckButton, "active", Gio.SettingsBindFlags.DEFAULT); 170 | 171 | // binds the color settings 172 | this._foregroundColorButton.connect("color-set", Lang.bind(this, function() { 173 | this._settings.set_string(FOREGROUND_COLOR_SETTING_KEY, this._foregroundColorButton.rgba.to_string()); 174 | this._updateForegroundColorButton(); 175 | })); 176 | 177 | this._backgroundColorButton.connect("color-set", Lang.bind(this, function() { 178 | this._settings.set_string(BACKGROUND_COLOR_SETTING_KEY, this._backgroundColorButton.rgba.to_string()); 179 | this._updateBackgroundColorButton(); 180 | })); 181 | 182 | foregroundColorResetButton.connect("clicked", Lang.bind(this, function() { 183 | this._settings.reset(FOREGROUND_COLOR_SETTING_KEY); 184 | this._updateForegroundColorButton(); 185 | })); 186 | 187 | backgroundColorResetButton.connect("clicked", Lang.bind(this, function() { 188 | this._settings.reset(BACKGROUND_COLOR_SETTING_KEY); 189 | this._updateBackgroundColorButton(); 190 | })); 191 | 192 | this._updateForegroundColorButton(); 193 | this._updateBackgroundColorButton(); 194 | 195 | // binds the transparency level 196 | transparencyLevelSpinButton.connect('value-changed', Lang.bind(this, function(button) { 197 | this._settings.set_uint(TRANSPARENCY_LEVEL_SETTING_KEY, button.get_value_as_int()); 198 | })); 199 | 200 | transparencyLevelSpinButton.set_value(this._settings.get_uint(TRANSPARENCY_LEVEL_SETTING_KEY)) 201 | 202 | // binds the terminal position setting 203 | // this._settings.bind(TERMINAL_POSITION_SETTING_KEY, positionComboBox, "active", Gio.SettingsBindFlags.DEFAULT); 204 | positionComboBox.set_active(this._settings.get_enum(TERMINAL_POSITION_SETTING_KEY)); 205 | positionComboBox.connect('changed', Lang.bind (this, function(widget) { 206 | this._settings.set_enum(TERMINAL_POSITION_SETTING_KEY, widget.get_active()); 207 | })); 208 | } 209 | }, 210 | 211 | _configureOtherShortcutTreeView: function(treeView) { 212 | let renderer = new Gtk.CellRendererAccel({editable: true}); 213 | renderer.connect("accel-edited", Lang.bind(this, this._otherShortcutAccelEdited)); 214 | renderer.connect("accel-cleared", Lang.bind(this, this._otherShortcutAccelCleared)); 215 | 216 | let column = new Gtk.TreeViewColumn(); 217 | column.pack_start(renderer, true); 218 | column.add_attribute(renderer, "accel-key", SHORTCUT_COLUMN_KEY); 219 | column.add_attribute(renderer, "accel-mods", 1); 220 | 221 | treeView.append_column(column); 222 | }, 223 | 224 | _shortcutTypeSettingChanged: function() { 225 | let otherShortcutType = this._settings.get_string(SHORTCUT_TYPE_SETTING_KEY) == "other"; 226 | 227 | if (this._otherShortcutRadioButton["active"] != otherShortcutType) { // guards against endless notification cycle 228 | this._otherShortcutRadioButton["active"] = otherShortcutType; 229 | } 230 | 231 | this._otherShortcutTreeView.set_sensitive(otherShortcutType); 232 | this._updateRealShortcut(); 233 | }, 234 | 235 | _otherShortcutSettingChanged: function() { 236 | this._updateOtherShortcutRow(this._settings.get_strv(OTHER_SHORTCUT_SETTING_KEY)[0]); 237 | this._updateRealShortcut(); 238 | }, 239 | 240 | _otherShortcutAccelEdited: function (renderer, path, key, mods, hwCode) { 241 | let accel = Gtk.accelerator_name(key, mods); 242 | 243 | this._updateOtherShortcutRow(accel); 244 | this._settings.set_strv(OTHER_SHORTCUT_SETTING_KEY, [accel]); 245 | }, 246 | 247 | _otherShortcutAccelCleared: function (renderer, path) { 248 | this._updateOtherShortcutRow(null); 249 | this._settings.set_strv(OTHER_SHORTCUT_SETTING_KEY, []); 250 | }, 251 | 252 | _updateRealShortcut: function() { 253 | let shortcutType = this._settings.get_string(SHORTCUT_TYPE_SETTING_KEY); 254 | 255 | if (shortcutType == "default") { 256 | this._settings.reset(REAL_SHORTCUT_SETTING_KEY); // the default of this key is the default shortcut 257 | } else { 258 | this._settings.set_strv(REAL_SHORTCUT_SETTING_KEY, this._settings.get_strv(OTHER_SHORTCUT_SETTING_KEY)); 259 | } 260 | }, 261 | 262 | _updateOtherShortcutRow: function(accel) { 263 | let [key, mods] = (accel !== null) ? Gtk.accelerator_parse(accel) : [0, 0]; 264 | this._otherShortcutListStore.set(this._otherShortcutRowIter, [SHORTCUT_COLUMN_KEY, SHORTCUT_COLUMN_MODS], [key, mods]); 265 | }, 266 | 267 | _updateForegroundColorButton: function() { 268 | this._foregroundColorButton.set_rgba(Convenience.parseRgbaColor(this._settings.get_string(FOREGROUND_COLOR_SETTING_KEY))); 269 | }, 270 | 271 | _updateBackgroundColorButton: function() { 272 | this._backgroundColorButton.set_rgba(Convenience.parseRgbaColor(this._settings.get_string(BACKGROUND_COLOR_SETTING_KEY))); 273 | }, 274 | 275 | _runCustomCommandCheckButtonToggled: function() { 276 | this._customCommandBox.set_sensitive(this._runCustomCommandCheckButton.get_active()); 277 | this._checkCustomCommandEntry(); 278 | }, 279 | 280 | _checkCustomCommandEntry: function() { 281 | let runCustomCommand = this._runCustomCommandCheckButton.get_active(); 282 | let error = null; 283 | 284 | if (runCustomCommand) { 285 | let customCommand = this._customCommandEntry.get_text().trim(); 286 | 287 | try { 288 | let [parsed, args] = GLib.shell_parse_argv(customCommand); 289 | 290 | if (!parsed) { 291 | error = _("no argument found"); 292 | } 293 | } catch (e) { 294 | error = e.message; 295 | } 296 | } 297 | 298 | this._customCommandEntry["secondary-icon-name"] = error ? "dialog-warning-symbolic" : null; 299 | this._customCommandEntry["secondary-icon-tooltip-text"] = error ? _("Error parsing command: %s").format(error) : null; 300 | } 301 | }); 302 | 303 | 304 | // preferences init hook 305 | function init() { 306 | 307 | } 308 | 309 | // preferences widget building hook 310 | function buildPrefsWidget() { 311 | let widget = new DropDownTerminalSettingsWidget(); 312 | widget.show_all(); 313 | 314 | return widget; 315 | } 316 | 317 | -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/stylesheet.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anarchodin/gs-extensions-drop-down-terminal/ec6d63458a3343ec3d92555bd6a533eca7b9b8f3/drop-down-terminal@gs-extensions.zzrough.org/stylesheet.css -------------------------------------------------------------------------------- /drop-down-terminal@gs-extensions.zzrough.org/terminal.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 Stéphane Démurget 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | // Author: Stéphane Démurget 17 | const Lang = imports.lang; 18 | 19 | const Pango = imports.gi.Pango; 20 | const Gdk = imports.gi.Gdk; 21 | const GdkX11 = imports.gi.GdkX11; 22 | const Gio = imports.gi.Gio; 23 | const GLib = imports.gi.GLib; 24 | const Gtk = imports.gi.Gtk; 25 | const Vte = imports.gi.Vte; 26 | 27 | const Convenience = imports.convenience; 28 | 29 | 30 | // dbus interface 31 | const DropDownTerminalIface = 32 | ' \ 33 | \ 34 | \ 35 | \ 36 | \ 37 | \ 38 | \ 39 | \ 40 | \ 41 | \ 42 | \ 43 | \ 44 | \ 45 | \ 46 | \ 47 | \ 48 | \ 49 | '; 50 | 51 | 52 | // uimanager popup information 53 | const PopupUi = 54 | ' \ 55 | \ 56 | \ 57 | \ 58 | \ 59 | '; 60 | 61 | 62 | 63 | // constants for the location of the extension 64 | const EXTENSION_ID = "drop-down-terminal"; 65 | const EXTENSION_UUID = EXTENSION_ID + "@gs-extensions.zzrough.org"; 66 | const EXTENSION_PATH = ARGV[0] || GLib.get_home_dir() + "/.local/share/gnome-shell/extensions/" + EXTENSION_UUID; 67 | 68 | 69 | // constants for the settings 70 | const FONT_NAME_SETTING_KEY = "monospace-font-name"; 71 | const TRANSPARENCY_LEVEL_SETTING_KEY = "transparency-level"; 72 | const TRANSPARENT_TERMINAL_SETTING_KEY = "transparent-terminal"; 73 | const SCROLLBAR_VISIBLE_SETTING_KEY = "scrollbar-visible"; 74 | const COLOR_FOREGROUND_SETTING_KEY = "foreground-color"; 75 | const COLOR_BACKGROUND_SETTING_KEY = "background-color"; 76 | const RUN_CUSTOM_COMMAND_SETTING_KEY = "run-custom-command"; 77 | const CUSTOM_COMMAND_SETTING_KEY = "custom-command"; 78 | const ENABLE_AUDIBLE_BELL_KEY = "enable-audible-bell"; 79 | 80 | // gnome desktop wm settings 81 | const WM_PREFERENCES_SCHEMA = "org.gnome.desktop.wm.preferences"; 82 | const WM_FOCUS_MODE_SETTING_KEY = "focus-mode"; 83 | const FOCUS_MODE_CLICK = "click"; 84 | const FOCUS_MODE_MOUSE = "mouse"; 85 | const FOCUS_MODE_SLOPPY = "sloppy"; 86 | 87 | // constants borrowed from gnome-terminal 88 | const ForegroundColor = Convenience.parseRgbaColor("#aaaaaaaaaaaa"); 89 | const BackgroundColor = Convenience.parseRgbaColor("#000000000000"); 90 | 91 | const TangoPalette = [ 92 | Convenience.parseRgbaColor("#000000000000"), 93 | Convenience.parseRgbaColor("#cccc00000000"), 94 | Convenience.parseRgbaColor("#4e4e9a9a0606"), 95 | Convenience.parseRgbaColor("#c4c4a0a00000"), 96 | Convenience.parseRgbaColor("#34346565a4a4"), 97 | Convenience.parseRgbaColor("#757550507b7b"), 98 | Convenience.parseRgbaColor("#060698209a9a"), 99 | Convenience.parseRgbaColor("#d3d3d7d7cfcf"), 100 | Convenience.parseRgbaColor("#555557575353"), 101 | Convenience.parseRgbaColor("#efef29292929"), 102 | Convenience.parseRgbaColor("#8a8ae2e23434"), 103 | Convenience.parseRgbaColor("#fcfce9e94f4f"), 104 | Convenience.parseRgbaColor("#72729f9fcfcf"), 105 | Convenience.parseRgbaColor("#adad7f7fa8a8"), 106 | Convenience.parseRgbaColor("#3434e2e2e2e2"), 107 | Convenience.parseRgbaColor("#eeeeeeeeecec") 108 | ]; 109 | 110 | const UserCharsPattern = "-[:alnum:]"; 111 | const UserCharsClassPattern = "[" + UserCharsPattern + "]"; 112 | const PassCharsClassPattern = "[-[:alnum:]\\Q,?;.:/!%$^*&~\"#'\\E]"; 113 | const HostCharsClassPattern = "[-[:alnum:]]"; 114 | const HostPattern = HostCharsClassPattern + "+(\\." + HostCharsClassPattern + "+)*"; 115 | const PortPattern = "(?:\\:[[:digit:]]{1,5})?"; 116 | const PathCharsClassPattern = "[-[:alnum:]\\Q_$.+!*,;@&=?/~#%\\E]"; 117 | const PathTermClassPattern = "[^\\Q]'.}>) \t\r\n,\"\\E]"; 118 | const SchemePattern = "(?:news:|telnet:|nntp:|file:\\/|https?:|ftps?:|sftp:|webcal:)"; 119 | const UserPassPattern = UserCharsClassPattern + "+(?:" + PassCharsClassPattern + "+)?"; 120 | const UrlPathPattern = "(?:(/" + UserCharsClassPattern + "+(?:[(]" 121 | + UserCharsClassPattern + "*[)])*" 122 | + UserCharsClassPattern + "*)*" + PathTermClassPattern + ")?"; 123 | 124 | const UriFlavor = { 125 | AsIs: 0, 126 | DefaultToHttp: 1, 127 | VoipCall: 2, 128 | Email: 3 129 | }; 130 | 131 | const UriHandlingProperties = [ 132 | { pattern: SchemePattern + "//(?:" + UserPassPattern + "\\@)?" + HostPattern + PortPattern + UrlPathPattern, flavor: UriFlavor.AsIs }, 133 | { pattern: "(?:www|ftp)" + HostCharsClassPattern + "*\\." + HostPattern + PortPattern + UrlPathPattern, flavor: UriFlavor.DefaultToHttp }, 134 | { pattern: "(?:callto:|h323:|sip:)" + UserCharsClassPattern + "[" + UserCharsPattern + ".]*(?:" + PortPattern + "/[a-z0-9]+)?\\@" + HostPattern, flavor: UriFlavor.VoipCall }, 135 | { pattern: "(?:mailto:)?" + UserCharsClassPattern + "[" + UserCharsPattern + ".]*\\@" + HostCharsClassPattern + "+\\." + HostPattern, flavor: UriFlavor.EMail }, 136 | { pattern: "(?:news:|man:|info:)[[:alnum:]\\Q^_{|}~!\"#$%&'()*+,./;:=?`\\E]+", flavor: UriFlavor.AsIs } 137 | ]; 138 | 139 | 140 | // terminal class 141 | const DropDownTerminal = new Lang.Class({ 142 | Name: "DropDownTerminal", 143 | 144 | _init: function() { 145 | // initializes the state 146 | this._customCommandArgs = []; 147 | this._lastForkFailed = false; 148 | this._visible = false; 149 | 150 | // loads the custom CSS to mimick the shell style 151 | let provider = new Gtk.CssProvider(); 152 | 153 | if (Convenience.GTK_VERSION >= 31790) { 154 | provider.load_from_file(Gio.File.new_for_path(EXTENSION_PATH + "/gtk.css")); 155 | } else if (Convenience.GTK_VERSION >= 31590) { 156 | provider.load_from_file(Gio.File.new_for_path(EXTENSION_PATH + "/gtk-3-16.css")); 157 | } else { 158 | provider.load_from_file(Gio.File.new_for_path(EXTENSION_PATH + "/gtk-3-14.css")); 159 | } 160 | 161 | Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); 162 | 163 | // creates the widgets and lays them out 164 | this._actionGroup = new Gtk.ActionGroup({name: "Main"}); 165 | this._terminal = this._createTerminal(); 166 | this._terminalSw = new Gtk.ScrolledWindow({ hadjustment: this._terminal.get_hadjustment(), 167 | vadjustment: this._terminal.get_vadjustment() }); 168 | this._window = this._createWindow(); 169 | this._popup = this._createPopupAndActions(this._window, this._actionGroup); 170 | 171 | this._terminalSw.add(this._terminal); 172 | this._window.add(this._terminalSw); 173 | 174 | this._terminal.show(); 175 | this._terminalSw.show(); 176 | 177 | // gets the settings 178 | this._settings = Convenience.getSettings(EXTENSION_PATH, EXTENSION_ID); 179 | this._interfaceSettings = new Gio.Settings({schema_id: "org.gnome.desktop.interface"}); 180 | 181 | // connect to the settings changes 182 | this._interfaceSettings.connect("changed::" + FONT_NAME_SETTING_KEY, Lang.bind(this, function() { 183 | Convenience.throttle(200, this, Convenience.gdkRunner(Lang.bind(this, this._updateFont))); 184 | })); 185 | 186 | this._settings.connect("changed::" + SCROLLBAR_VISIBLE_SETTING_KEY, Lang.bind(this, function() { 187 | Convenience.runInGdk(Lang.bind(this, this._updateOpacityAndColors)); 188 | })); 189 | 190 | this._settings.connect("changed::" + TRANSPARENCY_LEVEL_SETTING_KEY, Lang.bind(this, function() { 191 | Convenience.runInGdk(Lang.bind(this, this._updateOpacityAndColors)); 192 | })); 193 | 194 | this._settings.connect("changed::" + TRANSPARENT_TERMINAL_SETTING_KEY, Lang.bind(this, function() { 195 | Convenience.runInGdk(Lang.bind(this, this._updateOpacityAndColors)); 196 | })); 197 | 198 | this._settings.connect("changed::" + COLOR_FOREGROUND_SETTING_KEY, Lang.bind(this, function() { 199 | Convenience.runInGdk(Lang.bind(this, this._updateOpacityAndColors)); 200 | })); 201 | 202 | this._settings.connect("changed::" + COLOR_BACKGROUND_SETTING_KEY, Lang.bind(this, function() { 203 | Convenience.runInGdk(Lang.bind(this, this._updateOpacityAndColors)); 204 | })); 205 | 206 | this._settings.connect("changed::" + RUN_CUSTOM_COMMAND_SETTING_KEY, Lang.bind(this, this._updateCustomCommand)), 207 | this._settings.connect("changed::" + CUSTOM_COMMAND_SETTING_KEY, Lang.bind(this, this._updateCustomCommand)), 208 | 209 | this._settings.connect("changed::" + ENABLE_AUDIBLE_BELL_KEY, Lang.bind(this, this._updateAudibleIndicator)); 210 | 211 | // connect to gnome settings changes 212 | this._desktopSettings = Convenience.getInstalledSettings(WM_PREFERENCES_SCHEMA); 213 | if (this._desktopSettings != null) { 214 | this._desktopSettings.connect("changed::" + WM_FOCUS_MODE_SETTING_KEY, Lang.bind(this, this._updateFocusMode)); 215 | } 216 | 217 | // applies the settings initially 218 | this._updateFont(); 219 | this._updateOpacityAndColors(); 220 | this._updateAudibleIndicator(); 221 | this._updateCustomCommand(); 222 | this._updateFocusMode(); 223 | 224 | // adds the uri matchers 225 | this._uriHandlingPropertiesbyTag = {}; 226 | 227 | UriHandlingProperties.forEach(Lang.bind(this, function(hp) { 228 | let regex = GLib.Regex.new(hp.pattern, GLib.RegexCompileFlags.CASELESS | GLib.RegexCompileFlags.OPTIMIZE, 0); 229 | 230 | let tag = this._terminal.match_add_gregex(regex, 0); 231 | this._terminal.match_set_cursor_type(tag, Gdk.CursorType.HAND2); 232 | 233 | this._uriHandlingPropertiesbyTag[tag] = hp; 234 | })); 235 | 236 | // asks the session bus to own the interface name 237 | Gio.DBus.session.own_name("org.zzrough.GsExtensions.DropDownTerminal", 238 | Gio.BusNameOwnerFlags.NONE, 239 | null, 240 | null 241 | ); 242 | 243 | // exports the interface 244 | this._bus = Gio.DBusExportedObject.wrapJSObject(DropDownTerminalIface, this); 245 | this._bus.export(Gio.DBus.session, "/org/zzrough/GsExtensions/DropDownTerminal"); 246 | 247 | // forks the user shell early to detect a potential startup error 248 | this._forkUserShell(); 249 | }, 250 | 251 | get Pid() { 252 | return Convenience.getPid(); 253 | }, 254 | 255 | SetGeometry: function(x, y, width, height) { 256 | let [currentX, currentY] = this._window.get_position(); 257 | let [currentWidth, currentHeight] = this._window.get_size(); 258 | 259 | Convenience.runInGdk(Lang.bind(this, function() { 260 | if (x != currentX || y != currentY) { 261 | this._window.move(x, y); 262 | } 263 | 264 | if (width != currentWidth || height != currentHeight) { 265 | this._window.resize(width, height); 266 | } 267 | })); 268 | }, 269 | 270 | Toggle: function() { 271 | // update the window visibility in the UI thread since this callback happens in the gdbus thread 272 | Convenience.runInGdk(Lang.bind(this, function() { 273 | this._window.visible ? this._window.hide() 274 | : this._window.show(); 275 | 276 | return false; 277 | })); 278 | }, 279 | 280 | Focus: function() { 281 | // present the window in the UI thread since this callback happens in the gdbus thread 282 | Convenience.runInGdk(Lang.bind(this, function() { 283 | if (this._window.visible) { 284 | let time = 0; 285 | 286 | try { 287 | time = GdkX11.x11_get_server_time(this._window.window); 288 | } catch (e) { 289 | log("could not get x11 server time (cause: " + e + ")"); // not using logError as this is more an information than a real error 290 | } 291 | 292 | this._window.present_with_time(time); 293 | } 294 | })); 295 | }, 296 | 297 | Quit: function() { 298 | Gtk.main_quit(); 299 | }, 300 | 301 | _createTerminal: function() { 302 | let terminal = new Vte.Terminal(); 303 | 304 | terminal.set_can_focus(true); 305 | terminal.set_allow_bold(true); 306 | terminal.set_scroll_on_output(true); 307 | terminal.set_scroll_on_keystroke(true); 308 | terminal.set_scrollback_lines(8096); 309 | 310 | if (Vte.TerminalEraseBinding) { 311 | terminal.set_backspace_binding(Vte.TerminalEraseBinding.ASCII_DELETE); 312 | terminal.set_delete_binding(Vte.TerminalEraseBinding.DELETE_SEQUENCE); 313 | } 314 | 315 | if (terminal.set_word_chars) { 316 | terminal.set_word_chars("-A-Za-z0-9_$.+!*(),;:@&=?/~#%"); 317 | } 318 | 319 | terminal.set_encoding("UTF-8"); 320 | terminal.connect("eof", Lang.bind(this, this._forkUserShell)); 321 | terminal.connect("child-exited", Lang.bind(this, this._forkUserShell)); 322 | terminal.connect("button-release-event", Lang.bind(this, this._terminalButtonReleased)); 323 | terminal.connect("button-press-event", Lang.bind(this, this._terminalButtonPressed)); 324 | terminal.connect("refresh-window", Lang.bind(this, this._refreshWindow)); 325 | 326 | // FIXME: we get weird colors when we apply tango colors 327 | // 328 | // terminal.set_colors(ForegroundColor, BackgroundColor, TangoPalette, TangoPalette.length); 329 | 330 | return terminal; 331 | }, 332 | 333 | _createWindow: function() { 334 | let screen = Gdk.Screen.get_default(); 335 | let window = new Gtk.Window({type : Gtk.WindowType.TOPLEVEL}); 336 | 337 | window.set_title("Drop Down Terminal"); 338 | window.set_icon_name("utilities-terminal"); 339 | window.set_wmclass("Drop Down Terminal", "DropDownTerminalWindow"); 340 | window.set_decorated(false); 341 | window.set_skip_taskbar_hint(true); 342 | window.set_skip_pager_hint(true); 343 | window.set_resizable(true); 344 | window.set_keep_above(true); 345 | window.set_accept_focus(true); 346 | window.set_deletable(false); 347 | window.stick(); 348 | 349 | if (Convenience.GTK_VERSION >= 31800) { 350 | window.set_type_hint(Gdk.WindowTypeHint.DOCK); 351 | } else { 352 | window.set_type_hint(Gdk.WindowTypeHint.DROPDOWN_MENU); 353 | } 354 | 355 | window.set_visual(screen.get_rgba_visual()); 356 | 357 | window.connect("enter_notify_event", Lang.bind(this, this._windowMouseEnter)); 358 | window.connect("delete-event", function() { window.hide(); return true; }); 359 | window.connect("destroy", Gtk.main_quit); 360 | 361 | return window; 362 | }, 363 | 364 | _createPopupAndActions: function() { 365 | // get some shortcuts 366 | let term = this._terminal; 367 | let group = this._actionGroup; 368 | 369 | // creates the actions and fills the action group 370 | this._createAction("Copy", "Copy", Gtk.STOCK_COPY, "C", group, Lang.bind(term, term.copy_clipboard)); 371 | this._createAction("Paste", "Paste", Gtk.STOCK_PASTE, "V", group, Lang.bind(term, term.paste_clipboard)); 372 | 373 | // creates the UI manager 374 | let uiManager = new Gtk.UIManager(); 375 | uiManager.add_ui_from_string(PopupUi, PopupUi.length); 376 | uiManager.insert_action_group(group, 0); 377 | 378 | // hooks the accel group up 379 | this._window.add_accel_group(uiManager.get_accel_group()); 380 | 381 | return uiManager.get_widget("/TerminalPopup"); 382 | }, 383 | 384 | _forkUserShell: function() { 385 | this._terminal.reset(false, true); 386 | 387 | let args = this._getCommandArgs(); 388 | let success, pid; 389 | 390 | try { 391 | if (this._terminal.spawn_sync) { // 0.37.0 392 | [success, pid] = this._terminal.spawn_sync(Vte.PtyFlags.DEFAULT, GLib.get_home_dir(), args, this._getCommandEnv(), 393 | GLib.SpawnFlags.SEARCH_PATH, null, null); 394 | } else { 395 | [success, pid] = this._terminal.fork_command_full(Vte.PtyFlags.DEFAULT, GLib.get_home_dir(), args, this._getCommandEnv(), 396 | GLib.SpawnFlags.SEARCH_PATH, null); 397 | } 398 | 399 | this._lastForkFailed = false; 400 | } catch (e) { 401 | logError(e); 402 | 403 | this._lastForkFailed = true; 404 | 405 | let cause = e.name + " - " + e.message; 406 | 407 | this._bus.emit_signal("Failure", 408 | GLib.Variant.new("(ss)", ["ForkUserShellFailed", "Could not start the shell command line '" + args.join(" ") + "'."])); 409 | 410 | throw { 411 | name: "ForkUserShellFailed", 412 | message: "Could not start the shell from command line '" + args.join(" ") 413 | + "' (cause: " + cause + ")" 414 | } 415 | } 416 | 417 | if (this._terminal.get_pty) { // 0.37.0 418 | // (nothing, the default is the user choice at build-time, which defaults to xterm anyway) 419 | } else { 420 | this._terminal.get_pty_object().set_term("xterm"); 421 | } 422 | }, 423 | 424 | _refreshWindow: function() { 425 | let rect = this._window.window.get_frame_extents(); 426 | this._window.window.invalidate_rect(rect, true); 427 | }, 428 | 429 | _updateFont: function() { 430 | let fontDescStr = this._interfaceSettings.get_string(FONT_NAME_SETTING_KEY); 431 | let fontDesc = Pango.FontDescription.from_string(fontDescStr); 432 | 433 | this._terminal.set_font(fontDesc); 434 | }, 435 | 436 | _updateOpacityAndColors: function() { 437 | let isTransparent = this._settings.get_boolean(TRANSPARENT_TERMINAL_SETTING_KEY); 438 | let transparencyLevel = this._settings.get_uint(TRANSPARENCY_LEVEL_SETTING_KEY) / 100.0; 439 | let hasScrollbar = this._settings.get_boolean(SCROLLBAR_VISIBLE_SETTING_KEY); 440 | 441 | // updates the colors 442 | // 443 | // Note: to follow the deprecation scheme, we try first the _rgba variants as vte < 0.38 444 | // already has the non-rgba-suffixed one but it was working with GdkColor back then, 445 | // and passing a GdkRGBA would raise an exception 446 | let fgColor = Convenience.parseRgbaColor(this._settings.get_string(COLOR_FOREGROUND_SETTING_KEY)); 447 | let bgColor = Convenience.parseRgbaColor(this._settings.get_string(COLOR_BACKGROUND_SETTING_KEY)); 448 | 449 | if (this._terminal.set_color_foreground_rgba) { // removed in vte 0.38 450 | this._terminal.set_color_foreground_rgba(fgColor); 451 | } else { 452 | this._terminal.set_color_foreground(fgColor); 453 | } 454 | 455 | // Note: by applying the transparency only to the background colour of the terminal, the text stays 456 | // readable in any case 457 | bgColor.alpha = isTransparent ? transparencyLevel : bgColor.alpha; 458 | 459 | if (this._terminal.set_color_background_rgba) { // removed in vte 0.38 460 | this._terminal.set_color_background_rgba(bgColor); 461 | } else { 462 | this._terminal.set_color_background(bgColor); 463 | } 464 | 465 | this._terminalSw.set_policy(Gtk.PolicyType.AUTOMATIC, 466 | hasScrollbar ? Gtk.PolicyType.ALWAYS : Gtk.PolicyType.NEVER); 467 | }, 468 | 469 | _updateAudibleIndicator: function () { 470 | let enableBell = this._settings.get_boolean(ENABLE_AUDIBLE_BELL_KEY); 471 | this._terminal.set_audible_bell(enableBell); 472 | }, 473 | 474 | _updateCustomCommand: function() { 475 | // get the custom command 476 | let command; 477 | 478 | if (this._settings.get_boolean(RUN_CUSTOM_COMMAND_SETTING_KEY)) { 479 | command = this._settings.get_string(CUSTOM_COMMAND_SETTING_KEY).trim(); 480 | } else { 481 | command = ""; 482 | } 483 | 484 | // parses the command line 485 | this._customCommandArgs = command ? command.split(/\s+/) : []; 486 | 487 | // tries to fork the shell again if it fails last time (the user might be trying different values, 488 | // we do not want the terminal to get stuck) 489 | if (this._lastForkFailed) { 490 | this._forkUserShell(); 491 | } 492 | }, 493 | 494 | _updateFocusMode: function() { 495 | this._focusMode = this._desktopSettings ? this._desktopSettings.get_string(WM_FOCUS_MODE_SETTING_KEY) 496 | : FOCUS_MODE_CLICK; 497 | }, 498 | 499 | _windowMouseEnter: function(window, event) { 500 | if (this._focusMode != FOCUS_MODE_CLICK) { 501 | this.Focus(); 502 | } 503 | }, 504 | 505 | _terminalButtonPressed: function(terminal, event) { 506 | if (this._focusMode == FOCUS_MODE_CLICK) { 507 | this.Focus(); 508 | } 509 | }, 510 | 511 | _terminalButtonReleased: function(terminal, event) { 512 | let [has_state, state] = event.get_state(); 513 | let [is_button, button] = event.get_button(); 514 | 515 | // opens hovered link on ctrl+left-click 516 | if (is_button && button == Gdk.BUTTON_PRIMARY && (state & Gdk.ModifierType.CONTROL_MASK)) { 517 | let [preserved, x, y] = event.get_coords(); 518 | 519 | let border = new Gtk.Border(); 520 | terminal.style_get_property("inner-border", border); 521 | 522 | let column = (x - border.left) / terminal.get_char_width(); 523 | let row = (y - border.top) / terminal.get_char_height(); 524 | 525 | let [match, tag] = terminal.match_check(column, row); 526 | 527 | if (match) { 528 | let properties = this._uriHandlingPropertiesbyTag[tag]; 529 | this._openUri(match, properties.flavor, event.get_screen(), event.get_time()); 530 | } 531 | 532 | return true; 533 | } 534 | 535 | // opens the popup menu on right click (not using event.triggers_context_menu to avoid eating 536 | // Shift-F10 for Midnight Commander or an app like that) 537 | // 538 | // Note: we do not update the paste sensitivity as this requires API not available (Gdk.Atom and SELECTION_CLIPBOARD) 539 | // thus we do not handle copy sensitivity either (this makes more sense and is less code) 540 | if (is_button && button == Gdk.BUTTON_SECONDARY) { 541 | this._popup.popup(null, null, null, button, event.get_time()); 542 | return true; 543 | } 544 | 545 | return false; 546 | }, 547 | 548 | _openUri: function(uri, flavor, screen, time) { 549 | if (flavor == UriFlavor.DefaultToHttp) { 550 | uri = "http:" + uri; 551 | } else if (flavor == UriFlavor.Email && !uri.match(/^mailto:/i)) { 552 | uri = "mailto:" + uri; 553 | } 554 | 555 | Gtk.show_uri(screen, uri, time); 556 | }, 557 | 558 | _getCommandArgs: function() { 559 | // custom command 560 | if (this._customCommandArgs.length > 0) { 561 | return this._customCommandArgs; 562 | } 563 | 564 | // user shell 565 | try { 566 | let [parsed, args] = GLib.shell_parse_argv(Vte.get_user_shell()); 567 | 568 | if (parsed) { 569 | return args; 570 | } 571 | } catch (e) { 572 | // nothing: we continue silently as this is totally expected 573 | } 574 | 575 | // falls back to the classic Bourne shell 576 | return ["/bin/sh"]; 577 | }, 578 | 579 | _getCommandEnv: function() { 580 | // builds the environment 581 | let env = {}; 582 | 583 | GLib.listenv().forEach(function(name) { 584 | env[name] = GLib.getenv(name); 585 | }); 586 | 587 | delete env["COLUMNS"]; 588 | delete env["LINES"]; 589 | delete env["GNOME_DESKTOP_ICON"]; 590 | 591 | env["COLORTERM"] = "drop-down-terminal"; 592 | env["TERM"] = "xterm"; 593 | 594 | // gets an array of key=value pairs 595 | let envArray = []; 596 | 597 | for (let key in env) { 598 | envArray.push(key + "=" + (env[key] ? env[key] : "")); 599 | } 600 | 601 | return envArray; 602 | }, 603 | 604 | _createAction: function(name, label, stockId, accel, actionGroup, callback) { 605 | let action = new Gtk.Action({name: name, label: label, stock_id: stockId}); 606 | action.connect("activate", callback); 607 | actionGroup.add_action_with_accel(action, accel); 608 | 609 | return action; 610 | } 611 | }); 612 | 613 | 614 | // sets a nice program name and initializes gtk 615 | Gtk.init(null, 0); 616 | 617 | // sets the setting to prefer a dark theme 618 | Gtk.Settings.get_default()['gtk-application-prefer-dark-theme'] = true; 619 | 620 | // creates the terminal 621 | let terminal = new DropDownTerminal(); 622 | GLib.set_prgname("drop-down-terminal"); 623 | 624 | // starts the main loop 625 | Gtk.main(); 626 | 627 | -------------------------------------------------------------------------------- /screenshot-prefs-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anarchodin/gs-extensions-drop-down-terminal/ec6d63458a3343ec3d92555bd6a533eca7b9b8f3/screenshot-prefs-1.png -------------------------------------------------------------------------------- /screenshot-prefs-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anarchodin/gs-extensions-drop-down-terminal/ec6d63458a3343ec3d92555bd6a533eca7b9b8f3/screenshot-prefs-2.png -------------------------------------------------------------------------------- /screenshot-term.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anarchodin/gs-extensions-drop-down-terminal/ec6d63458a3343ec3d92555bd6a533eca7b9b8f3/screenshot-term.png --------------------------------------------------------------------------------