├── .gitignore
├── LICENSE
├── Makefile
├── README.org
├── default.nix
├── emacs-module.h
├── evil-collection-webkit.el
├── hints.css
├── hints.js
├── screencast.gif
├── shell.nix
├── tests.el
├── webkit-ace.el
├── webkit-dark.el
├── webkit-history.el
├── webkit-module.c
└── webkit.el
/.gitignore:
--------------------------------------------------------------------------------
1 | *.so
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | LIBS = gtk+-3.0 webkit2gtk-4.0
2 | CFLAGS = -std=c99 -Wall -Wextra -Wno-unused-parameter -Wl,--no-as-needed -fpic
3 | CFLAGS += `pkg-config --cflags $(LIBS)`
4 | LDFLAGS += `pkg-config --libs $(LIBS)`
5 |
6 | all : webkit-module.so
7 |
8 | debug: CFLAGS += -DDEBUG -g
9 | debug: webkit-module.so
10 |
11 | webkit-module.so : webkit-module.c
12 | $(CC) -shared $(CFLAGS) $(LDFLAGS) -o $@ $^
13 |
14 | clean :
15 | $(RM) webkit-module.so
16 |
17 | .PHONY : clean all
18 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | #+TITLE: ~emacs-webkit~
2 | #+SUBTITLE: A successor to ~xwidget-webkit~
3 |
4 | #+html: 
5 |
6 | ** Update
7 |
8 | I haven't had time since originally creating this to work further on it.
9 | Meanwhile bugs with xwidget on pgtk have been smoothed out upstream and some minimal maintenance continues to happen there.
10 | Thus if you want a browser in emacs, I recommend building with xwidgets (which also supports macos).
11 | I don't plan to continue any development on this for both technical and personal reasons, but I think it represents an interesting (albeit somewhat hacky) approach to pushing what is possible with emacs dynamic modules.
12 |
13 | ** Warning 1!
14 |
15 | This package is still in the early stages of active development. While I am
16 | currently using it daily, it should be considered as still in an alpha
17 | stage. This means that the public interface can and likely will change! Also,
18 | due to the nature of dynamic modules dealing with memory directly, bugs can mean
19 | segfaults, memory leaks, and ultimately untimely crashes. So beware that data
20 | loss may result! However, if you do experience crashes, please report it!
21 |
22 | ** Warning 2!
23 |
24 | Browsers are a big target of malware and as such ensuring you're running the
25 | most up to date version that receives security patches is essential. It is up to
26 | you to ensure you are running an up to date version of webkitgtk2. Note that not
27 | every distro consistently and quickly updates webkitgtk2 in its package
28 | repos. The WebKitGTK team publishes releases and security advisories [[https://webkitgtk.org/news.html][here]].
29 |
30 | Running a browser capable of javascript execution inside Emacs is potentially
31 | scary since inside Emacs, all code is trusted and usually has unrestricted
32 | access to your system. This is somewhat mitigated by webkitgtk2 using a
33 | multiprocess model where website's content and scripts run in a separate process
34 | from UI, which in this case is Emacs. I'll make every effort to ensure that
35 | potentially malicious JavaScript cannot remotely execute arbitrary lisp, however
36 | my trust model will always treat user lisp code as trusted. Furthermore I make
37 | no grantees about the overall security of ~emacs-webkit~.
38 |
39 | * Background
40 |
41 | I've found that the only two applications I regularly have open are Emacs and a
42 | browser. Emacs is a joy to use while I feel like I'm constantly fighting my
43 | browser. I used to be pretty happy with Firefox and vimperator however since
44 | Project Quantum came to FF 57+, I haven't been satisfied with the extensibility
45 | or speed of the WebExtension replacements. There are a few keyboard driven,
46 | extensible browser projects such as qutebrowser, LuaKit, vimb, surf,
47 | etc.... Unfortunately given the complexity of the "modern" web, they all have to
48 | be based off of either the Blink (through QtWebEngine) or WebKit (through
49 | WebKitGTK) browser engines (I really wish Mozilla would put more effort into
50 | making a clean and maintained API for embedding the latest Gecko with rust
51 | components, i.e. servo). I shy away from Blink based browsers out of principles
52 | of wanting to avoid Google and the browser monoculture and also because the only
53 | easy way to embed Blink is through QtWebEngine and I don't really like Qt all
54 | that much.
55 |
56 | An attempt at adding a webkitgtk widget to Emacs with the experimental
57 | ~xwidgets~ feature [[https://github.com/jave/xwidget-emacs][was made many years ago]] and has received pretty minimal
58 | development over the years. I think it isn't for lack of interest in the
59 | concept, but rather the difficulty of understanding the complicated dance that
60 | is Emacs redisplay and how xwidgets hacked themselves into that. Also the
61 | "politics" of having a full featured browser inside Emacs core that can
62 | potentially execute non-free javascript usually means much discussion on
63 | emacs-devel when trying to add new features. I figured that perhaps such a
64 | feature could be implemented instead as a dynamic module. This has the advantage
65 | of a clearer separation between Emacs' display handling and webkit's, hopefully
66 | making it easier to workout the inevitable bugs that occur when forcing them to
67 | coexist. This also allows features and fixes to be developed outside of Emacs
68 | core with less concern for supporting all the platforms and environments that
69 | Emacs needs to work with, while also avoiding some of the "political tensions".
70 |
71 | ~emacs-webkit~ now has all of the features that the ~webkit xwidget~ has, plus
72 | more, such as integrating some features from [[https://github.com/canatella/xwwp][~xwwp~]]. I was also able to add
73 | experimental support for opening an non-embedded, dedicated webkit window. In
74 | principle this allows running ~emacs-webkit~ on a tty as webkit popus up its own
75 | window (of course one needs a graphical session to be running, i.e. ~$DISPLAY~
76 | needs to be set). In testing this, I've found there will always be behavior out
77 | of Emacs' control due to the window being at the mercy of the window manager, so
78 | things like focusing end up wonky and harder to control. To try this out: ~(setq
79 | webkit-own-window t)~.
80 |
81 | ** But what about the Emacs Application Framework (EAF)?
82 |
83 | While I think its neat what EAF has been able to do, I personally have less of a
84 | desire to dive into its code due to its reliance on Qt (and hence Blink for its
85 | browser component) and python, and I suspect this may be a barrier for others as
86 | well. Furthermore, I don't think it has a technical path forward to natively
87 | work on Wayland due to its current reliance on the XEmbed protocol. My goal with
88 | ~emacs-webkit~ was to have something that will work with pgtk port of Emacs (in
89 | fact I primarily developed it on Wayland running pgtk Emacs).
90 |
91 | ** But what about nyxt?
92 |
93 | I think nyxt is certainly a cool project and I wish them the best! However, I
94 | would say this project is for the Emacs user like me who begrudgingly uses a
95 | modern web browser but wishes they didn't have to. I wish I could make eww my
96 | default browser but it just very often doesn't cut it due to the unfortunate
97 | world of "modern" javascript and "web apps". In contrast, I would say nyxt is
98 | more for the lisp aficionado who wishes the web ran lisp instead of javascript
99 | and the Emacs user who wishes Emacs' underlying UI paradigm looked more like the
100 | web's DOM. I believe the nyxt developers want nyxt to essentially be a common
101 | lisp emacs. It is a massive undertaking and will take time for them to build an
102 | ecosystem like the one Emacs has developed over the decades. I've thought about
103 | how I could make nyxt integrate with Emacs in a way I would be happy with and
104 | I've found that while it is certainly possible to do so given nyxt's
105 | extensibility, I felt like there would always be some friction. For example
106 | who's UI should I use? Do I integrate Emacs buffer list into nyxt's minibuffer
107 | or nyxts buffer list into Emacs? Finally I wanted an excuse to dig more into
108 | Emacs' C guts and this project has given me a lot of chances to do so.
109 |
110 | * Installation
111 |
112 | Once things stabilize a bit, I'll probably package this for MELPA.
113 |
114 | ~emacs-webkit~ requires at least Emacs 28
115 |
116 | Make sure you have gcc, pkg-config, gtk3, glib-networking, and of course
117 | webkitgtk installed. Then just run ~make~ to make ~webkit-module.so~.
118 |
119 | Some package managers support custom build steps to automate building. For
120 | example with the [[https://github.com/raxod502/straight.el/][straight.el]] develop branch you can use this recipe
121 |
122 | #+begin_src emacs-lisp
123 | (straight-use-package
124 | '(webkit :type git :host github :repo "akirakyle/emacs-webkit"
125 | :branch "main"
126 | :files (:defaults "*.js" "*.css" "*.so")
127 | :pre-build ("make")))
128 | #+end_src
129 |
130 | I'm a bit hesitant to add lisp code to do this automagically or fetch prebuilt
131 | modules from the web like ~pdf-tools~ or ~emacs-libvterm~, because I'm a
132 | believer that it should be the job of a package manager, but perhaps I'll be
133 | convinced otherwise.
134 |
135 | * Setup
136 |
137 | First ensure ~emacs-webkit~ is on your ~load-path~.
138 |
139 | ** Manually
140 |
141 | #+begin_src emacs-lisp
142 | (require 'webkit)
143 | (global-set-key (kbd "s-b") 'webkit) ;; Bind to whatever global key binding you want if you want
144 | (require 'webkit-ace) ;; If you want link hinting
145 | (require 'webkit-dark) ;; If you want to use the simple dark mode
146 | #+end_src
147 |
148 | ** ~use-package~
149 |
150 | #+begin_src emacs-lisp
151 | (use-package webkit
152 | :bind ("s-b" 'webkit)) ;; Bind to whatever global key binding you want if you want
153 | (use-package 'webkit-ace) ;; If you want link hinting
154 | (use-package 'webkit-dark) ;; If you want to use the simple dark mode
155 | #+end_src
156 |
157 | * Usage
158 |
159 | - ~M-x webkit~
160 | - Enter url or keywords to search
161 | - ~C-h m~ (~describe-mode~) to see keybindings.
162 | - Feel the power (and weight) of a browser running inside Emacs.
163 | - Emacs' builtin bookmarks and ~org-store-link~ are supported!
164 |
165 | ~emacs-webkit~ has a concept of an "insert" mode, which moves keyboard focus to
166 | the ~webview~ from Emacs. This means the webview will see all key-presses and
167 | Emacs will only see the modifier keypresses that are unhandled by the
168 | webview. This is useful for typing in a text box or using the keyboard shortcuts
169 | a website might set up. To return focus back to Emacs use ~C-g~. Some
170 | ~emacs-webkit~ features might have a javascript component that requires moving
171 | to insert mode. Sometimes javascript is buggy or crashes in which case you may
172 | be left surprised that Emacs isn't responding to you. ~C-g~ is, as always, your
173 | friend here.
174 |
175 | ~webkit-start-web-inspector~ will start webkit's built in dev tools. Beware that
176 | ~C-g~ cannot escape from web inspector's focus but ~C-~ appears to return
177 | focus to the webkit view (there doesn't appear to be much ~emac-webkit~ can do
178 | about this).
179 |
180 | * Customization
181 |
182 | #+begin_src emacs-lisp
183 | ;; If you don't care so much about privacy and want to give your data to google
184 | (setq webkit-search-prefix "https://google.com/search?q=")
185 |
186 | ;; Specify a different set of characters use in the link hints
187 | ;; For example the following are more convienent if you use dvorak
188 | (setq webkit-ace-chars "aoeuidhtns")
189 |
190 | ;; If you want history saved in a different place or
191 | ;; Set to `nil' to if you don't want history saved to file (will stay in memory)
192 | (setq webkit-history-file "~/path/to/webkit-history")
193 |
194 | ;; If you want cookies saved in a different place or
195 | ;; Set to `nil' to if you don't want cookies saved
196 | (setq webkit-cookie-file "~/path/to/cookies")
197 |
198 | ;; See the above explination in the Background section
199 | ;; This must be set before webkit.el is loaded so certain hooks aren't installed
200 | (setq webkit-own-window t)
201 |
202 | ;; Set webkit as the default browse-url browser
203 | (setq browse-url-browser-function 'webkit-browse-url)
204 |
205 | ;; Force webkit to always open a new session instead of reusing a current one
206 | (setq webkit-browse-url-force-new t)
207 |
208 | ;; Globally disable javascript
209 | (add-hook 'webkit-new-hook #'webkit-enable-javascript)
210 |
211 | ;; Override the "loading:" mode line indicator with an icon from `all-the-icons.el'
212 | ;; You could also use a unicode icon like ↺
213 | (defun webkit--display-progress (progress)
214 | (setq webkit--progress-formatted
215 | (if (equal progress 100.0)
216 | ""
217 | (format "%s%.0f%% " (all-the-icons-faicon "spinner") progress)))
218 | (force-mode-line-update))
219 |
220 | ;; Set action to be taken on a download request. Predefined actions are
221 | ;; `webkit-download-default', `webkit-download-save', and `webkit-download-open'
222 | ;; where the save function saves to the download directory, the open function
223 | ;; opens in a temp buffer and the default function interactively prompts.
224 | (setq webkit-download-action-alist '(("\\.pdf\\'" . webkit-download-open)
225 | ("\\.png\\'" . webkit-download-save)
226 | (".*" . webkit-download-default))
227 |
228 | ;; Globally use a proxy
229 | (add-hook 'webkit-new-hook (lambda () (webkit-set-proxy "socks://localhost:8000")))
230 |
231 | ;; Globally use the simple dark mode
232 | (setq webkit-dark-mode t)
233 | #+end_src
234 |
235 | I personally use evil so I've included ~evil-collection~ bindings which I hope
236 | to upstream at some point when things stabilize.
237 |
238 | #+begin_src emacs-lisp
239 | (use-package evil-collection-webkit
240 | :config
241 | (evil-collection-xwidget-setup))
242 | #+end_src
243 |
244 | * TODO Roadmap (roughly in order of my priorities)
245 | - Ad block
246 | - Edit text areas in temp emacs buffer
247 | - Pass integration
248 | - XEmbed when using emacs ~--with-x~
249 | - Browsing sessions/data and better cookie management
250 | - Web extensions?
251 | - Echo url on mouse hover
252 | - ~completing-read~ link completion/heading jumping
253 | - History ~display-table~ mode
254 | - favicon on mode line
255 |
--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import {}
2 | , trivialBuild ? pkgs.emacsPackages.trivialBuild
3 | # user arguments
4 | , packageSrc ? ./.
5 | , packageVersion ? "git"
6 | }:
7 |
8 | trivialBuild rec {
9 | pname = "emacs-webkit";
10 |
11 | src = packageSrc;
12 | version = packageVersion;
13 |
14 | buildPhase = ''
15 | make all
16 | export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
17 | '';
18 | nativeBuildInputs = with pkgs; [ pkg-config wrapGAppsHook ];
19 | gstBuildInputs = with pkgs; with gst_all_1; [
20 | gstreamer gst-libav
21 | gst-plugins-base
22 | gst-plugins-good
23 | gst-plugins-bad
24 | gst-plugins-ugly
25 | ];
26 | buildInputs = with pkgs; [
27 | webkitgtk
28 | glib gtk3
29 | glib-networking
30 | gsettings-desktop-schemas
31 | ] ++ gstBuildInputs;
32 |
33 | GIO_EXTRA_MODULES = "${pkgs.glib-networking}/lib/gio/modules:${pkgs.dconf.lib}/lib/gio/modules";
34 | GST_PLUGIN_SYSTEM_PATH_1_0 = pkgs.lib.concatMapStringsSep ":" (p: "${p}/lib/gstreamer-1.0") gstBuildInputs;
35 |
36 | postInstall = ''
37 | cp *.so *.js *.css $out/share/emacs/site-lisp/
38 | mkdir $out/lib && cp *.so $out/lib/
39 | '';
40 | }
41 |
--------------------------------------------------------------------------------
/emacs-module.h:
--------------------------------------------------------------------------------
1 | /* emacs-module.h - GNU Emacs module API.
2 |
3 | Copyright (C) 2015-2020 Free Software Foundation, Inc.
4 |
5 | This file is part of GNU Emacs.
6 |
7 | GNU Emacs is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or (at
10 | your option) any later version.
11 |
12 | GNU Emacs is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with GNU Emacs. If not, see . */
19 |
20 | /*
21 | This file defines the Emacs module API. Please see the chapter
22 | `Dynamic Modules' in the GNU Emacs Lisp Reference Manual for
23 | information how to write modules and use this header file.
24 | */
25 |
26 | #ifndef EMACS_MODULE_H
27 | #define EMACS_MODULE_H
28 |
29 | #include
30 | #include
31 | #include
32 |
33 | #ifndef __cplusplus
34 | #include
35 | #endif
36 |
37 | #define EMACS_MAJOR_VERSION 28
38 |
39 | #if defined __cplusplus && __cplusplus >= 201103L
40 | # define EMACS_NOEXCEPT noexcept
41 | #else
42 | # define EMACS_NOEXCEPT
43 | #endif
44 |
45 | #if defined __cplusplus && __cplusplus >= 201703L
46 | # define EMACS_NOEXCEPT_TYPEDEF noexcept
47 | #else
48 | # define EMACS_NOEXCEPT_TYPEDEF
49 | #endif
50 |
51 | #if 3 < __GNUC__ + (3 <= __GNUC_MINOR__)
52 | # define EMACS_ATTRIBUTE_NONNULL(...) \
53 | __attribute__ ((__nonnull__ (__VA_ARGS__)))
54 | #elif defined __has_attribute
55 | # if __has_attribute (__nonnull__)
56 | # define EMACS_ATTRIBUTE_NONNULL(...) \
57 | __attribute__ ((__nonnull__ (__VA_ARGS__)))
58 | # endif
59 | #endif
60 | #ifndef EMACS_ATTRIBUTE_NONNULL
61 | # define EMACS_ATTRIBUTE_NONNULL(...)
62 | #endif
63 |
64 | #ifdef __cplusplus
65 | extern "C" {
66 | #endif
67 |
68 | /* Current environment. */
69 | typedef struct emacs_env_28 emacs_env;
70 |
71 | /* Opaque pointer representing an Emacs Lisp value.
72 | BEWARE: Do not assume NULL is a valid value! */
73 | typedef struct emacs_value_tag *emacs_value;
74 |
75 | enum { emacs_variadic_function = -2 };
76 |
77 | /* Struct passed to a module init function (emacs_module_init). */
78 | struct emacs_runtime
79 | {
80 | /* Structure size (for version checking). */
81 | ptrdiff_t size;
82 |
83 | /* Private data; users should not touch this. */
84 | struct emacs_runtime_private *private_members;
85 |
86 | /* Return an environment pointer. */
87 | emacs_env *(*get_environment) (struct emacs_runtime *runtime)
88 | EMACS_ATTRIBUTE_NONNULL (1);
89 | };
90 |
91 | /* Type aliases for function pointer types used in the module API.
92 | Note that we don't use these aliases directly in the API to be able
93 | to mark the function arguments as 'noexcept' before C++20.
94 | However, users can use them if they want. */
95 |
96 | /* Function prototype for the module Lisp functions. These must not
97 | throw C++ exceptions. */
98 | typedef emacs_value (*emacs_function) (emacs_env *env, ptrdiff_t nargs,
99 | emacs_value *args,
100 | void *data)
101 | EMACS_NOEXCEPT_TYPEDEF EMACS_ATTRIBUTE_NONNULL (1);
102 |
103 | /* Function prototype for module user-pointer and function finalizers.
104 | These must not throw C++ exceptions. */
105 | typedef void (*emacs_finalizer) (void *data) EMACS_NOEXCEPT_TYPEDEF;
106 |
107 | /* Possible Emacs function call outcomes. */
108 | enum emacs_funcall_exit
109 | {
110 | /* Function has returned normally. */
111 | emacs_funcall_exit_return = 0,
112 |
113 | /* Function has signaled an error using `signal'. */
114 | emacs_funcall_exit_signal = 1,
115 |
116 | /* Function has exit using `throw'. */
117 | emacs_funcall_exit_throw = 2
118 | };
119 |
120 | /* Possible return values for emacs_env.process_input. */
121 | enum emacs_process_input_result
122 | {
123 | /* Module code may continue */
124 | emacs_process_input_continue = 0,
125 |
126 | /* Module code should return control to Emacs as soon as possible. */
127 | emacs_process_input_quit = 1
128 | };
129 |
130 | /* Define emacs_limb_t so that it is likely to match GMP's mp_limb_t.
131 | This micro-optimization can help modules that use mpz_export and
132 | mpz_import, which operate more efficiently on mp_limb_t. It's OK
133 | (if perhaps a bit slower) if the two types do not match, and
134 | modules shouldn't rely on the two types matching. */
135 | typedef size_t emacs_limb_t;
136 | #define EMACS_LIMB_MAX SIZE_MAX
137 |
138 | struct emacs_env_25
139 | {
140 | /* Structure size (for version checking). */
141 | ptrdiff_t size;
142 |
143 | /* Private data; users should not touch this. */
144 | struct emacs_env_private *private_members;
145 |
146 | /* Memory management. */
147 |
148 | emacs_value (*make_global_ref) (emacs_env *env, emacs_value value)
149 | EMACS_ATTRIBUTE_NONNULL(1);
150 |
151 | void (*free_global_ref) (emacs_env *env, emacs_value global_value)
152 | EMACS_ATTRIBUTE_NONNULL(1);
153 |
154 | /* Non-local exit handling. */
155 |
156 | enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env)
157 | EMACS_ATTRIBUTE_NONNULL(1);
158 |
159 | void (*non_local_exit_clear) (emacs_env *env)
160 | EMACS_ATTRIBUTE_NONNULL(1);
161 |
162 | enum emacs_funcall_exit (*non_local_exit_get)
163 | (emacs_env *env, emacs_value *symbol, emacs_value *data)
164 | EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
165 |
166 | void (*non_local_exit_signal) (emacs_env *env,
167 | emacs_value symbol, emacs_value data)
168 | EMACS_ATTRIBUTE_NONNULL(1);
169 |
170 | void (*non_local_exit_throw) (emacs_env *env,
171 | emacs_value tag, emacs_value value)
172 | EMACS_ATTRIBUTE_NONNULL(1);
173 |
174 | /* Function registration. */
175 |
176 | emacs_value (*make_function) (emacs_env *env,
177 | ptrdiff_t min_arity,
178 | ptrdiff_t max_arity,
179 | emacs_value (*func) (emacs_env *env,
180 | ptrdiff_t nargs,
181 | emacs_value* args,
182 | void *data)
183 | EMACS_NOEXCEPT
184 | EMACS_ATTRIBUTE_NONNULL(1),
185 | const char *docstring,
186 | void *data)
187 | EMACS_ATTRIBUTE_NONNULL(1, 4);
188 |
189 | emacs_value (*funcall) (emacs_env *env,
190 | emacs_value func,
191 | ptrdiff_t nargs,
192 | emacs_value* args)
193 | EMACS_ATTRIBUTE_NONNULL(1);
194 |
195 | emacs_value (*intern) (emacs_env *env, const char *name)
196 | EMACS_ATTRIBUTE_NONNULL(1, 2);
197 |
198 | /* Type conversion. */
199 |
200 | emacs_value (*type_of) (emacs_env *env, emacs_value arg)
201 | EMACS_ATTRIBUTE_NONNULL(1);
202 |
203 | bool (*is_not_nil) (emacs_env *env, emacs_value arg)
204 | EMACS_ATTRIBUTE_NONNULL(1);
205 |
206 | bool (*eq) (emacs_env *env, emacs_value a, emacs_value b)
207 | EMACS_ATTRIBUTE_NONNULL(1);
208 |
209 | intmax_t (*extract_integer) (emacs_env *env, emacs_value arg)
210 | EMACS_ATTRIBUTE_NONNULL(1);
211 |
212 | emacs_value (*make_integer) (emacs_env *env, intmax_t n)
213 | EMACS_ATTRIBUTE_NONNULL(1);
214 |
215 | double (*extract_float) (emacs_env *env, emacs_value arg)
216 | EMACS_ATTRIBUTE_NONNULL(1);
217 |
218 | emacs_value (*make_float) (emacs_env *env, double d)
219 | EMACS_ATTRIBUTE_NONNULL(1);
220 |
221 | /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
222 | NUL-terminated string.
223 |
224 | SIZE must point to the total size of the buffer. If BUFFER is
225 | NULL or if SIZE is not big enough, write the required buffer size
226 | to SIZE and return true.
227 |
228 | Note that SIZE must include the last NUL byte (e.g. "abc" needs
229 | a buffer of size 4).
230 |
231 | Return true if the string was successfully copied. */
232 |
233 | bool (*copy_string_contents) (emacs_env *env,
234 | emacs_value value,
235 | char *buf,
236 | ptrdiff_t *len)
237 | EMACS_ATTRIBUTE_NONNULL(1, 4);
238 |
239 | /* Create a Lisp string from a utf8 encoded string. */
240 | emacs_value (*make_string) (emacs_env *env,
241 | const char *str, ptrdiff_t len)
242 | EMACS_ATTRIBUTE_NONNULL(1, 2);
243 |
244 | /* Embedded pointer type. */
245 | emacs_value (*make_user_ptr) (emacs_env *env,
246 | void (*fin) (void *) EMACS_NOEXCEPT,
247 | void *ptr)
248 | EMACS_ATTRIBUTE_NONNULL(1);
249 |
250 | void *(*get_user_ptr) (emacs_env *env, emacs_value arg)
251 | EMACS_ATTRIBUTE_NONNULL(1);
252 | void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr)
253 | EMACS_ATTRIBUTE_NONNULL(1);
254 |
255 | void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr))
256 | (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1);
257 | void (*set_user_finalizer) (emacs_env *env, emacs_value arg,
258 | void (*fin) (void *) EMACS_NOEXCEPT)
259 | EMACS_ATTRIBUTE_NONNULL(1);
260 |
261 | /* Vector functions. */
262 | emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index)
263 | EMACS_ATTRIBUTE_NONNULL(1);
264 |
265 | void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index,
266 | emacs_value value)
267 | EMACS_ATTRIBUTE_NONNULL(1);
268 |
269 | ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector)
270 | EMACS_ATTRIBUTE_NONNULL(1);
271 | };
272 |
273 | struct emacs_env_26
274 | {
275 | /* Structure size (for version checking). */
276 | ptrdiff_t size;
277 |
278 | /* Private data; users should not touch this. */
279 | struct emacs_env_private *private_members;
280 |
281 | /* Memory management. */
282 |
283 | emacs_value (*make_global_ref) (emacs_env *env, emacs_value value)
284 | EMACS_ATTRIBUTE_NONNULL(1);
285 |
286 | void (*free_global_ref) (emacs_env *env, emacs_value global_value)
287 | EMACS_ATTRIBUTE_NONNULL(1);
288 |
289 | /* Non-local exit handling. */
290 |
291 | enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env)
292 | EMACS_ATTRIBUTE_NONNULL(1);
293 |
294 | void (*non_local_exit_clear) (emacs_env *env)
295 | EMACS_ATTRIBUTE_NONNULL(1);
296 |
297 | enum emacs_funcall_exit (*non_local_exit_get)
298 | (emacs_env *env, emacs_value *symbol, emacs_value *data)
299 | EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
300 |
301 | void (*non_local_exit_signal) (emacs_env *env,
302 | emacs_value symbol, emacs_value data)
303 | EMACS_ATTRIBUTE_NONNULL(1);
304 |
305 | void (*non_local_exit_throw) (emacs_env *env,
306 | emacs_value tag, emacs_value value)
307 | EMACS_ATTRIBUTE_NONNULL(1);
308 |
309 | /* Function registration. */
310 |
311 | emacs_value (*make_function) (emacs_env *env,
312 | ptrdiff_t min_arity,
313 | ptrdiff_t max_arity,
314 | emacs_value (*func) (emacs_env *env,
315 | ptrdiff_t nargs,
316 | emacs_value* args,
317 | void *data)
318 | EMACS_NOEXCEPT
319 | EMACS_ATTRIBUTE_NONNULL(1),
320 | const char *docstring,
321 | void *data)
322 | EMACS_ATTRIBUTE_NONNULL(1, 4);
323 |
324 | emacs_value (*funcall) (emacs_env *env,
325 | emacs_value func,
326 | ptrdiff_t nargs,
327 | emacs_value* args)
328 | EMACS_ATTRIBUTE_NONNULL(1);
329 |
330 | emacs_value (*intern) (emacs_env *env, const char *name)
331 | EMACS_ATTRIBUTE_NONNULL(1, 2);
332 |
333 | /* Type conversion. */
334 |
335 | emacs_value (*type_of) (emacs_env *env, emacs_value arg)
336 | EMACS_ATTRIBUTE_NONNULL(1);
337 |
338 | bool (*is_not_nil) (emacs_env *env, emacs_value arg)
339 | EMACS_ATTRIBUTE_NONNULL(1);
340 |
341 | bool (*eq) (emacs_env *env, emacs_value a, emacs_value b)
342 | EMACS_ATTRIBUTE_NONNULL(1);
343 |
344 | intmax_t (*extract_integer) (emacs_env *env, emacs_value arg)
345 | EMACS_ATTRIBUTE_NONNULL(1);
346 |
347 | emacs_value (*make_integer) (emacs_env *env, intmax_t n)
348 | EMACS_ATTRIBUTE_NONNULL(1);
349 |
350 | double (*extract_float) (emacs_env *env, emacs_value arg)
351 | EMACS_ATTRIBUTE_NONNULL(1);
352 |
353 | emacs_value (*make_float) (emacs_env *env, double d)
354 | EMACS_ATTRIBUTE_NONNULL(1);
355 |
356 | /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
357 | NUL-terminated string.
358 |
359 | SIZE must point to the total size of the buffer. If BUFFER is
360 | NULL or if SIZE is not big enough, write the required buffer size
361 | to SIZE and return true.
362 |
363 | Note that SIZE must include the last NUL byte (e.g. "abc" needs
364 | a buffer of size 4).
365 |
366 | Return true if the string was successfully copied. */
367 |
368 | bool (*copy_string_contents) (emacs_env *env,
369 | emacs_value value,
370 | char *buf,
371 | ptrdiff_t *len)
372 | EMACS_ATTRIBUTE_NONNULL(1, 4);
373 |
374 | /* Create a Lisp string from a utf8 encoded string. */
375 | emacs_value (*make_string) (emacs_env *env,
376 | const char *str, ptrdiff_t len)
377 | EMACS_ATTRIBUTE_NONNULL(1, 2);
378 |
379 | /* Embedded pointer type. */
380 | emacs_value (*make_user_ptr) (emacs_env *env,
381 | void (*fin) (void *) EMACS_NOEXCEPT,
382 | void *ptr)
383 | EMACS_ATTRIBUTE_NONNULL(1);
384 |
385 | void *(*get_user_ptr) (emacs_env *env, emacs_value arg)
386 | EMACS_ATTRIBUTE_NONNULL(1);
387 | void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr)
388 | EMACS_ATTRIBUTE_NONNULL(1);
389 |
390 | void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr))
391 | (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1);
392 | void (*set_user_finalizer) (emacs_env *env, emacs_value arg,
393 | void (*fin) (void *) EMACS_NOEXCEPT)
394 | EMACS_ATTRIBUTE_NONNULL(1);
395 |
396 | /* Vector functions. */
397 | emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index)
398 | EMACS_ATTRIBUTE_NONNULL(1);
399 |
400 | void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index,
401 | emacs_value value)
402 | EMACS_ATTRIBUTE_NONNULL(1);
403 |
404 | ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector)
405 | EMACS_ATTRIBUTE_NONNULL(1);
406 |
407 | /* Returns whether a quit is pending. */
408 | bool (*should_quit) (emacs_env *env)
409 | EMACS_ATTRIBUTE_NONNULL(1);
410 | };
411 |
412 | struct emacs_env_27
413 | {
414 | /* Structure size (for version checking). */
415 | ptrdiff_t size;
416 |
417 | /* Private data; users should not touch this. */
418 | struct emacs_env_private *private_members;
419 |
420 | /* Memory management. */
421 |
422 | emacs_value (*make_global_ref) (emacs_env *env, emacs_value value)
423 | EMACS_ATTRIBUTE_NONNULL(1);
424 |
425 | void (*free_global_ref) (emacs_env *env, emacs_value global_value)
426 | EMACS_ATTRIBUTE_NONNULL(1);
427 |
428 | /* Non-local exit handling. */
429 |
430 | enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env)
431 | EMACS_ATTRIBUTE_NONNULL(1);
432 |
433 | void (*non_local_exit_clear) (emacs_env *env)
434 | EMACS_ATTRIBUTE_NONNULL(1);
435 |
436 | enum emacs_funcall_exit (*non_local_exit_get)
437 | (emacs_env *env, emacs_value *symbol, emacs_value *data)
438 | EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
439 |
440 | void (*non_local_exit_signal) (emacs_env *env,
441 | emacs_value symbol, emacs_value data)
442 | EMACS_ATTRIBUTE_NONNULL(1);
443 |
444 | void (*non_local_exit_throw) (emacs_env *env,
445 | emacs_value tag, emacs_value value)
446 | EMACS_ATTRIBUTE_NONNULL(1);
447 |
448 | /* Function registration. */
449 |
450 | emacs_value (*make_function) (emacs_env *env,
451 | ptrdiff_t min_arity,
452 | ptrdiff_t max_arity,
453 | emacs_value (*func) (emacs_env *env,
454 | ptrdiff_t nargs,
455 | emacs_value* args,
456 | void *data)
457 | EMACS_NOEXCEPT
458 | EMACS_ATTRIBUTE_NONNULL(1),
459 | const char *docstring,
460 | void *data)
461 | EMACS_ATTRIBUTE_NONNULL(1, 4);
462 |
463 | emacs_value (*funcall) (emacs_env *env,
464 | emacs_value func,
465 | ptrdiff_t nargs,
466 | emacs_value* args)
467 | EMACS_ATTRIBUTE_NONNULL(1);
468 |
469 | emacs_value (*intern) (emacs_env *env, const char *name)
470 | EMACS_ATTRIBUTE_NONNULL(1, 2);
471 |
472 | /* Type conversion. */
473 |
474 | emacs_value (*type_of) (emacs_env *env, emacs_value arg)
475 | EMACS_ATTRIBUTE_NONNULL(1);
476 |
477 | bool (*is_not_nil) (emacs_env *env, emacs_value arg)
478 | EMACS_ATTRIBUTE_NONNULL(1);
479 |
480 | bool (*eq) (emacs_env *env, emacs_value a, emacs_value b)
481 | EMACS_ATTRIBUTE_NONNULL(1);
482 |
483 | intmax_t (*extract_integer) (emacs_env *env, emacs_value arg)
484 | EMACS_ATTRIBUTE_NONNULL(1);
485 |
486 | emacs_value (*make_integer) (emacs_env *env, intmax_t n)
487 | EMACS_ATTRIBUTE_NONNULL(1);
488 |
489 | double (*extract_float) (emacs_env *env, emacs_value arg)
490 | EMACS_ATTRIBUTE_NONNULL(1);
491 |
492 | emacs_value (*make_float) (emacs_env *env, double d)
493 | EMACS_ATTRIBUTE_NONNULL(1);
494 |
495 | /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
496 | NUL-terminated string.
497 |
498 | SIZE must point to the total size of the buffer. If BUFFER is
499 | NULL or if SIZE is not big enough, write the required buffer size
500 | to SIZE and return true.
501 |
502 | Note that SIZE must include the last NUL byte (e.g. "abc" needs
503 | a buffer of size 4).
504 |
505 | Return true if the string was successfully copied. */
506 |
507 | bool (*copy_string_contents) (emacs_env *env,
508 | emacs_value value,
509 | char *buf,
510 | ptrdiff_t *len)
511 | EMACS_ATTRIBUTE_NONNULL(1, 4);
512 |
513 | /* Create a Lisp string from a utf8 encoded string. */
514 | emacs_value (*make_string) (emacs_env *env,
515 | const char *str, ptrdiff_t len)
516 | EMACS_ATTRIBUTE_NONNULL(1, 2);
517 |
518 | /* Embedded pointer type. */
519 | emacs_value (*make_user_ptr) (emacs_env *env,
520 | void (*fin) (void *) EMACS_NOEXCEPT,
521 | void *ptr)
522 | EMACS_ATTRIBUTE_NONNULL(1);
523 |
524 | void *(*get_user_ptr) (emacs_env *env, emacs_value arg)
525 | EMACS_ATTRIBUTE_NONNULL(1);
526 | void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr)
527 | EMACS_ATTRIBUTE_NONNULL(1);
528 |
529 | void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr))
530 | (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1);
531 | void (*set_user_finalizer) (emacs_env *env, emacs_value arg,
532 | void (*fin) (void *) EMACS_NOEXCEPT)
533 | EMACS_ATTRIBUTE_NONNULL(1);
534 |
535 | /* Vector functions. */
536 | emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index)
537 | EMACS_ATTRIBUTE_NONNULL(1);
538 |
539 | void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index,
540 | emacs_value value)
541 | EMACS_ATTRIBUTE_NONNULL(1);
542 |
543 | ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector)
544 | EMACS_ATTRIBUTE_NONNULL(1);
545 |
546 | /* Returns whether a quit is pending. */
547 | bool (*should_quit) (emacs_env *env)
548 | EMACS_ATTRIBUTE_NONNULL(1);
549 |
550 | /* Processes pending input events and returns whether the module
551 | function should quit. */
552 | enum emacs_process_input_result (*process_input) (emacs_env *env)
553 | EMACS_ATTRIBUTE_NONNULL (1);
554 |
555 | struct timespec (*extract_time) (emacs_env *env, emacs_value arg)
556 | EMACS_ATTRIBUTE_NONNULL (1);
557 |
558 | emacs_value (*make_time) (emacs_env *env, struct timespec time)
559 | EMACS_ATTRIBUTE_NONNULL (1);
560 |
561 | bool (*extract_big_integer) (emacs_env *env, emacs_value arg, int *sign,
562 | ptrdiff_t *count, emacs_limb_t *magnitude)
563 | EMACS_ATTRIBUTE_NONNULL (1);
564 |
565 | emacs_value (*make_big_integer) (emacs_env *env, int sign, ptrdiff_t count,
566 | const emacs_limb_t *magnitude)
567 | EMACS_ATTRIBUTE_NONNULL (1);
568 | };
569 |
570 | struct emacs_env_28
571 | {
572 | /* Structure size (for version checking). */
573 | ptrdiff_t size;
574 |
575 | /* Private data; users should not touch this. */
576 | struct emacs_env_private *private_members;
577 |
578 | /* Memory management. */
579 |
580 | emacs_value (*make_global_ref) (emacs_env *env, emacs_value value)
581 | EMACS_ATTRIBUTE_NONNULL(1);
582 |
583 | void (*free_global_ref) (emacs_env *env, emacs_value global_value)
584 | EMACS_ATTRIBUTE_NONNULL(1);
585 |
586 | /* Non-local exit handling. */
587 |
588 | enum emacs_funcall_exit (*non_local_exit_check) (emacs_env *env)
589 | EMACS_ATTRIBUTE_NONNULL(1);
590 |
591 | void (*non_local_exit_clear) (emacs_env *env)
592 | EMACS_ATTRIBUTE_NONNULL(1);
593 |
594 | enum emacs_funcall_exit (*non_local_exit_get)
595 | (emacs_env *env, emacs_value *symbol, emacs_value *data)
596 | EMACS_ATTRIBUTE_NONNULL(1, 2, 3);
597 |
598 | void (*non_local_exit_signal) (emacs_env *env,
599 | emacs_value symbol, emacs_value data)
600 | EMACS_ATTRIBUTE_NONNULL(1);
601 |
602 | void (*non_local_exit_throw) (emacs_env *env,
603 | emacs_value tag, emacs_value value)
604 | EMACS_ATTRIBUTE_NONNULL(1);
605 |
606 | /* Function registration. */
607 |
608 | emacs_value (*make_function) (emacs_env *env,
609 | ptrdiff_t min_arity,
610 | ptrdiff_t max_arity,
611 | emacs_value (*func) (emacs_env *env,
612 | ptrdiff_t nargs,
613 | emacs_value* args,
614 | void *data)
615 | EMACS_NOEXCEPT
616 | EMACS_ATTRIBUTE_NONNULL(1),
617 | const char *docstring,
618 | void *data)
619 | EMACS_ATTRIBUTE_NONNULL(1, 4);
620 |
621 | emacs_value (*funcall) (emacs_env *env,
622 | emacs_value func,
623 | ptrdiff_t nargs,
624 | emacs_value* args)
625 | EMACS_ATTRIBUTE_NONNULL(1);
626 |
627 | emacs_value (*intern) (emacs_env *env, const char *name)
628 | EMACS_ATTRIBUTE_NONNULL(1, 2);
629 |
630 | /* Type conversion. */
631 |
632 | emacs_value (*type_of) (emacs_env *env, emacs_value arg)
633 | EMACS_ATTRIBUTE_NONNULL(1);
634 |
635 | bool (*is_not_nil) (emacs_env *env, emacs_value arg)
636 | EMACS_ATTRIBUTE_NONNULL(1);
637 |
638 | bool (*eq) (emacs_env *env, emacs_value a, emacs_value b)
639 | EMACS_ATTRIBUTE_NONNULL(1);
640 |
641 | intmax_t (*extract_integer) (emacs_env *env, emacs_value arg)
642 | EMACS_ATTRIBUTE_NONNULL(1);
643 |
644 | emacs_value (*make_integer) (emacs_env *env, intmax_t n)
645 | EMACS_ATTRIBUTE_NONNULL(1);
646 |
647 | double (*extract_float) (emacs_env *env, emacs_value arg)
648 | EMACS_ATTRIBUTE_NONNULL(1);
649 |
650 | emacs_value (*make_float) (emacs_env *env, double d)
651 | EMACS_ATTRIBUTE_NONNULL(1);
652 |
653 | /* Copy the content of the Lisp string VALUE to BUFFER as an utf8
654 | NUL-terminated string.
655 |
656 | SIZE must point to the total size of the buffer. If BUFFER is
657 | NULL or if SIZE is not big enough, write the required buffer size
658 | to SIZE and return true.
659 |
660 | Note that SIZE must include the last NUL byte (e.g. "abc" needs
661 | a buffer of size 4).
662 |
663 | Return true if the string was successfully copied. */
664 |
665 | bool (*copy_string_contents) (emacs_env *env,
666 | emacs_value value,
667 | char *buf,
668 | ptrdiff_t *len)
669 | EMACS_ATTRIBUTE_NONNULL(1, 4);
670 |
671 | /* Create a Lisp string from a utf8 encoded string. */
672 | emacs_value (*make_string) (emacs_env *env,
673 | const char *str, ptrdiff_t len)
674 | EMACS_ATTRIBUTE_NONNULL(1, 2);
675 |
676 | /* Embedded pointer type. */
677 | emacs_value (*make_user_ptr) (emacs_env *env,
678 | void (*fin) (void *) EMACS_NOEXCEPT,
679 | void *ptr)
680 | EMACS_ATTRIBUTE_NONNULL(1);
681 |
682 | void *(*get_user_ptr) (emacs_env *env, emacs_value arg)
683 | EMACS_ATTRIBUTE_NONNULL(1);
684 | void (*set_user_ptr) (emacs_env *env, emacs_value arg, void *ptr)
685 | EMACS_ATTRIBUTE_NONNULL(1);
686 |
687 | void (*(*get_user_finalizer) (emacs_env *env, emacs_value uptr))
688 | (void *) EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL(1);
689 | void (*set_user_finalizer) (emacs_env *env, emacs_value arg,
690 | void (*fin) (void *) EMACS_NOEXCEPT)
691 | EMACS_ATTRIBUTE_NONNULL(1);
692 |
693 | /* Vector functions. */
694 | emacs_value (*vec_get) (emacs_env *env, emacs_value vector, ptrdiff_t index)
695 | EMACS_ATTRIBUTE_NONNULL(1);
696 |
697 | void (*vec_set) (emacs_env *env, emacs_value vector, ptrdiff_t index,
698 | emacs_value value)
699 | EMACS_ATTRIBUTE_NONNULL(1);
700 |
701 | ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vector)
702 | EMACS_ATTRIBUTE_NONNULL(1);
703 |
704 | /* Returns whether a quit is pending. */
705 | bool (*should_quit) (emacs_env *env)
706 | EMACS_ATTRIBUTE_NONNULL(1);
707 |
708 | /* Processes pending input events and returns whether the module
709 | function should quit. */
710 | enum emacs_process_input_result (*process_input) (emacs_env *env)
711 | EMACS_ATTRIBUTE_NONNULL (1);
712 |
713 | struct timespec (*extract_time) (emacs_env *env, emacs_value arg)
714 | EMACS_ATTRIBUTE_NONNULL (1);
715 |
716 | emacs_value (*make_time) (emacs_env *env, struct timespec time)
717 | EMACS_ATTRIBUTE_NONNULL (1);
718 |
719 | bool (*extract_big_integer) (emacs_env *env, emacs_value arg, int *sign,
720 | ptrdiff_t *count, emacs_limb_t *magnitude)
721 | EMACS_ATTRIBUTE_NONNULL (1);
722 |
723 | emacs_value (*make_big_integer) (emacs_env *env, int sign, ptrdiff_t count,
724 | const emacs_limb_t *magnitude)
725 | EMACS_ATTRIBUTE_NONNULL (1);
726 |
727 | /* Add module environment functions newly added in Emacs 28 here.
728 | Before Emacs 28 is released, remove this comment and start
729 | module-env-29.h on the master branch. */
730 |
731 | void (*(*EMACS_ATTRIBUTE_NONNULL (1)
732 | get_function_finalizer) (emacs_env *env,
733 | emacs_value arg)) (void *) EMACS_NOEXCEPT;
734 |
735 | void (*set_function_finalizer) (emacs_env *env, emacs_value arg,
736 | void (*fin) (void *) EMACS_NOEXCEPT)
737 | EMACS_ATTRIBUTE_NONNULL (1);
738 |
739 | int (*open_channel) (emacs_env *env, emacs_value pipe_process)
740 | EMACS_ATTRIBUTE_NONNULL (1);
741 |
742 | void (*make_interactive) (emacs_env *env, emacs_value function,
743 | emacs_value spec)
744 | EMACS_ATTRIBUTE_NONNULL (1);
745 | };
746 |
747 | /* Every module should define a function as follows. */
748 | extern int emacs_module_init (struct emacs_runtime *runtime)
749 | EMACS_NOEXCEPT
750 | EMACS_ATTRIBUTE_NONNULL (1);
751 |
752 | #ifdef __cplusplus
753 | }
754 | #endif
755 |
756 | #endif /* EMACS_MODULE_H */
757 |
--------------------------------------------------------------------------------
/evil-collection-webkit.el:
--------------------------------------------------------------------------------
1 | ;;; evil-collection-webkit.el --- Evil bindings for webkit -*- lexical-binding: t -*-
2 |
3 | ;; Copyright (C) 2020 Akira Kyle
4 |
5 | ;; Author: Akira Kyle
6 | ;; Maintainer: Akira Kyle
7 | ;; URL: https://github.com/emacs-evil/evil-collection
8 | ;; Version: 0.0.1
9 | ;; Package-Requires: ((emacs "25.1"))
10 | ;; Keywords: evil, webkit, tools
11 |
12 | ;; This file is free software; you can redistribute it and/or modify
13 | ;; it under the terms of the GNU General Public License as published
14 | ;; by the Free Software Foundation; either version 3, or (at your
15 | ;; option) any later version.
16 | ;;
17 | ;; This file is distributed in the hope that it will be useful,
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 | ;; GNU General Public License for more details.
21 | ;;
22 | ;; For a full copy of the GNU General Public License
23 | ;; see .
24 |
25 | ;;; Commentary:
26 | ;; Evil bindings for webkit.
27 |
28 | ;;; Code:
29 | (require 'webkit)
30 | (require 'evil-collection)
31 |
32 | (defvar evil-collection-webkit-maps '(webkit-mode-map))
33 |
34 | (defun webkit-scroll-up-half (&optional webkit-id)
35 | (interactive)
36 | (webkit-scroll-by-percent 0.5 webkit-id))
37 |
38 | (defun webkit-scroll-down-half (&optional webkit-id)
39 | (interactive)
40 | (webkit-scroll-by-percent -0.5 webkit-id))
41 |
42 | (defvar webkit--evil-escape-script "
43 | function WKViewEvilKeyDown(event) {
44 | console.log('WKViewKeyDown: '+event.key);
45 | if (event.key == 'Escape') {
46 | window.webkit.messageHandlers['webkit--callback-unfocus'].postMessage('');
47 | }
48 | }
49 | document.addEventListener('keydown', WKViewEvilKeyDown);
50 | ")
51 |
52 | (defun evil-collection-webkit-escape ()
53 | (webkit-add-script webkit--evil-escape-script))
54 |
55 | (defun evil-collection-webkit-insert-on-insert ()
56 | (add-hook 'evil-insert-state-entry-hook 'webkit-insert-mode nil t))
57 |
58 | (defun evil-collection-webkit-unfocus-to-normal-mode (val)
59 | (ignore val)
60 | (evil-normal-state))
61 |
62 | ;;;###autoload
63 | (defun evil-collection-xwidget-setup ()
64 | "Set up `evil' bindings for `webkit'."
65 | (evil-collection-define-key 'normal 'webkit-mode-map
66 | "q" 'quit-window
67 | "k" 'webkit-scroll-down-line
68 | "j" 'webkit-scroll-up-line
69 | "h" 'webkit-scroll-backward
70 | "l" 'webkit-scroll-forward
71 | "d" 'webkit-scroll-up-half
72 | "u" 'webkit-scroll-down-half
73 | (kbd "C-f") 'webkit-scroll-up
74 | (kbd "C-b") 'webkit-scroll-down
75 | "+" 'webkit-zoom-in
76 | "=" 'webkit-zoom-in
77 | "-" 'webkit-zoom-out
78 | "f" 'webkit-ace
79 | "/" 'webkit-isearch
80 | "n" 'webkit-search-next
81 | "N" 'webkit-search-previous
82 | "ESC" 'webkit-search-finish
83 | "R" 'webkit-reload
84 | "gr" 'webkit-reload
85 | "H" 'webkit-back
86 | "L" 'webkit-forward
87 | "gu" 'webkit
88 | "gg" 'webkit-scroll-top
89 | "G" 'webkit-scroll-bottom
90 | "y" 'webkit-copy-selection
91 | "Y" 'webkit-copy-url
92 | "zd" 'webkit-dark-toggle)
93 |
94 | (when evil-want-C-d-scroll
95 | (evil-collection-define-key 'normal 'webkit-mode-map
96 | (kbd "C-d") 'webkit-scroll-up-half))
97 | (when evil-want-C-u-scroll
98 | (evil-collection-define-key 'normal 'webkit-mode-map
99 | (kbd "C-u") 'webkit-scroll-down-half))
100 |
101 | (add-hook 'webkit-mode-hook #'evil-collection-webkit-insert-on-insert)
102 | (add-hook 'webkit-new-hook #'evil-collection-webkit-escape)
103 | (advice-add 'webkit--callback-unfocus
104 | :after #'evil-collection-webkit-unfocus-to-normal-mode)
105 | )
106 |
107 | ;; add advice around to go back to normal mode
108 |
109 | (provide 'evil-collection-webkit)
110 | ;;; evil-collection-webkit.el ends here
111 |
--------------------------------------------------------------------------------
/hints.css:
--------------------------------------------------------------------------------
1 | *[webkitviewhint^='hint']
2 | {
3 | all: initial;
4 | z-index: 2147483647;
5 | position: fixed !important;
6 | opacity: 0.7;
7 | border:1px solid #444;
8 | color: red;
9 | background-color: yellow;
10 | font: bold 20px monospace !important;
11 | margin: 0;
12 | padding: 0px 1px;
13 | }
14 |
--------------------------------------------------------------------------------
/hints.js:
--------------------------------------------------------------------------------
1 | var __WKViewHints = Object.freeze((function(){
2 | 'use strict';
3 | var hints = [];
4 |
5 | function addHint(elem, hintText) {
6 | let bounding = elem.getBoundingClientRect();
7 | if (bounding.top >= 0 &&
8 | bounding.left >= 0 &&
9 | bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
10 | bounding.right <= (window.innerWidth || document.documentElement.clientWidth) &&
11 | (elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem.getClientRects().length > 0) &&
12 | hints.every(
13 | function (other) {
14 | let other_bounding = other.getBoundingClientRect();
15 | return !(Math.abs(other_bounding.top - bounding.top) < 5
16 | && Math.abs(other_bounding.left - bounding.left) < 5)
17 | })
18 | ){
19 | let hint = document.createElement('div');
20 | hint.setAttribute('webkitviewhint', 'hint');
21 | hint.style.left = bounding.left + 'px';
22 | hint.style.top = bounding.top + 'px';
23 | elem.appendChild(hint);
24 | hint.appendChild(document.createTextNode(hintText));
25 | hints.push(hint);
26 | }
27 | }
28 |
29 | return {
30 | init: function(hintKeys) {
31 | let N = hintKeys.length
32 | let tags = 'button, input, [href], select, textarea, [tabindex]:not([tabindex="-1"])';
33 | let elems = document.querySelectorAll(tags);
34 | let hintPadLen = Math.ceil(Math.log(elems.length)/Math.log(N))
35 | let idxToHintText = function (idx) {
36 | return idx.toString(N).padStart(hintPadLen, '0').split('').map(
37 | digit => hintKeys.charAt(parseInt(digit, N))).join('');};
38 |
39 | elems.forEach((elem, idx) => addHint(elem, idxToHintText(idx)));
40 |
41 | return hints.length;
42 | },
43 | update: function(key) {
44 | let newHints = hints.filter(hint => hint.innerText.startsWith(key));
45 | if (newHints.length > 1){
46 | hints.forEach(function (hint) {
47 | if (!hint.innerText.startsWith(key))
48 | hint.remove();
49 | });
50 | newHints.forEach(function (hint) {
51 | hint.innerText = hint.innerText.substring(1)
52 | });
53 | hints = newHints;
54 | return hints.length;
55 | }
56 | else if (newHints.length == 1){
57 | let selected = newHints[0].parentNode;
58 | console.log(selected);
59 | hints.forEach(hint => hint.remove());
60 | hints = [];
61 | selected.focus();
62 | selected.click();
63 | return 1;
64 | }
65 | else {
66 | hints.forEach(hint => hint.remove());
67 | hints = [];
68 | return -1;
69 | }
70 | },
71 | };
72 | })());
73 |
74 | // Local Variables:
75 | // js-indent-level: 2
76 | // End:
77 |
--------------------------------------------------------------------------------
/screencast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akirakyle/emacs-webkit/4c5caa8e2c2baa09400d3c4a467d4799d735d388/screencast.gif
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import {} }:
2 | pkgs.mkShell {
3 | buildInputs = with pkgs; [
4 | gcc pkg-config gtk3 webkitgtk glib-networking wrapGAppsHook
5 | gdb
6 | ];
7 |
8 | GIO_EXTRA_MODULES = "${pkgs.glib-networking}/lib/gio/modules:${pkgs.dconf.lib}/lib/gio/modules";
9 | }
10 |
--------------------------------------------------------------------------------
/tests.el:
--------------------------------------------------------------------------------
1 | ;; Not really tests per se, but rather a dump of elisp I've used to test stuff
2 | (defun fake-module-reload (module)
3 | (interactive "Reload Module file: ")
4 | (let ((tmpfile (make-temp-file
5 | (file-name-nondirectory module) nil module-file-suffix)))
6 | (copy-file module tmpfile t)
7 | (module-load tmpfile)))
8 |
9 | (add-to-list 'load-path "~/git/emacs-webkit")
10 | (fake-module-reload (expand-file-name "~/git/emacs-webkit/webkit-module.so"))
11 | ;;(setq debug-on-error t)
12 |
13 | (require 'webkit)
14 | (require 'evil-collection-webkit)
15 | (require 'webkit-ace)
16 |
17 | (evil-collection-xwidget-setup)
18 |
19 | (webkit-browse-url "http://xkcd.com" t)
20 | (setq webkit-own-window t)
21 | (garbage-collect)
22 |
23 | ;;(setq my-pipe (get-buffer-process (cdr (car webkit--id-buffer-alist))))
24 | (with-current-buffer (car webkit--buffers) (buffer-string))
25 | (setq webkit--id (with-current-buffer (car webkit--buffers) webkit--id))
26 | (setq webkit--id nil)
27 | (with-current-buffer (car webkit--buffers) webkit--styles)
28 |
29 | (webkit--xid-to-pointer (string-to-number (frame-parameter (selected-frame) 'window-id)))
30 | (eq 'x (window-system))
31 |
32 | (setq my-params (frame-parameters))
33 | (while my-params
34 | (message "%S" (car my-params))
35 | (setq my-params (cdr my-params)))
36 | (modify-frame-parameters nil '((inhibit-double-buffering . t)))
37 |
38 | (setq webkit--script (webkit--file-to-string
39 | (expand-file-name "script.js" webkit-base)))
40 | (webkit--execute-js webkit--id
41 | "webkitHints('aoeuhtns');" "message")
42 | (webkit--execute-js webkit--id "alert(\"hi\")")
43 | (webkit--execute-js webkit--id "\"hi\"" "message")
44 | (webkit--add-user-script webkit--id "alert(\"hi\")")
45 | (webkit--remove-all-user-scripts webkit--id)
46 | (webkit--register-script-message webkit--id "message")
47 | (webkit--unregister-script-message webkit--id "message")
48 |
49 | (webkit--execute-js webkit--id
50 | "window.webkit.messageHandlers.message.postMessage(\"hi\")")
51 |
52 | (webkit--execute-js webkit--id
53 | "window.webkit.messageHandlers[\"webkit--callback-key-down\"].postMessage(\"hi\")"
54 | "message")
55 |
56 | (webkit--proxy-set-uri webkit--id "socks://localhost:8000")
57 | (webkit--proxy-set-default webkit--id)
58 |
59 | (setq webkit--to-json-js "
60 | function toJSON(node) {
61 | let propFix = { for: 'htmlFor', class: 'className' };
62 | let specialGetters = {
63 | style: (node) => node.style.cssText,
64 | };
65 | let attrDefaultValues = { style: '' };
66 | let obj = {
67 | nodeType: node.nodeType,
68 | };
69 | if (node.tagName) {
70 | obj.tagName = node.tagName.toLowerCase();
71 | } else if (node.nodeName) {
72 | obj.nodeName = node.nodeName;
73 | }
74 | if (node.nodeValue) {
75 | obj.nodeValue = node.nodeValue;
76 | }
77 | let attrs = node.attributes;
78 | if (attrs) {
79 | let defaultValues = new Map();
80 | for (let i = 0; i < attrs.length; i++) {
81 | let name = attrs[i].nodeName;
82 | defaultValues.set(name, attrDefaultValues[name]);
83 | }
84 | // Add some special cases that might not be included by enumerating
85 | // attributes above. Note: this list is probably not exhaustive.
86 | switch (obj.tagName) {
87 | case 'input': {
88 | if (node.type === 'checkbox' || node.type === 'radio') {
89 | defaultValues.set('checked', false);
90 | } else if (node.type !== 'file') {
91 | // Don't store the value for a file input.
92 | defaultValues.set('value', '');
93 | }
94 | break;
95 | }
96 | case 'option': {
97 | defaultValues.set('selected', false);
98 | break;
99 | }
100 | case 'textarea': {
101 | defaultValues.set('value', '');
102 | break;
103 | }
104 | }
105 | let arr = [];
106 | for (let [name, defaultValue] of defaultValues) {
107 | let propName = propFix[name] || name;
108 | let specialGetter = specialGetters[propName];
109 | let value = specialGetter ? specialGetter(node) : node[propName];
110 | if (value !== defaultValue) {
111 | arr.push([name, value]);
112 | }
113 | }
114 | if (arr.length) {
115 | obj.attributes = arr;
116 | }
117 | }
118 | let childNodes = node.childNodes;
119 | // Don't process children for a textarea since we used `value` above.
120 | if (obj.tagName !== 'textarea' && childNodes && childNodes.length) {
121 | let arr = (obj.childNodes = []);
122 | for (let i = 0; i < childNodes.length; i++) {
123 | arr[i] = toJSON(childNodes[i]);
124 | }
125 | }
126 | return obj;
127 | }
128 | toJSON(document);
129 | ")
130 |
131 | (defun webkit--save-json (msg)
132 | (setq webkit--json (json-parse-string msg)))
133 |
134 | (webkit--execute-js
135 | (with-current-buffer (car webkit--buffers) webkit--id)
136 | webkit--to-json-js "webkit--save-json")
137 |
138 | (defun webkit--echo-uri (uri)
139 | (message uri))
140 |
141 | (add-hook 'webkit-uri-changed-functions 'webkit--echo-uri)
142 |
143 | (setq webkit-uri-changed-functions nil)
144 | (setq webkit-progress-changed-functions nil)
145 |
146 | (remove-hook 'window-size-change-functions #'webkit--adjust-size)
147 | (webkit--show webkit--id)
148 |
149 | (webkit--resize webkit--id 50 50 200 400)
150 |
151 | (setq test-hist nil)
152 | (maphash (lambda (k v)
153 | (push (cons k v) test-hist))
154 | webkit-history-table)
155 |
--------------------------------------------------------------------------------
/webkit-ace.el:
--------------------------------------------------------------------------------
1 | ;;; webkit-ace.el --- ace for webkit dynamic module -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2020 Akira Kyle
4 |
5 | ;; Author: Akira Kyle
6 | ;; URL: https://github.com/akirakyle/emacs-webkit
7 | ;; Version: 0.1
8 | ;; Package-Requires: ((emacs "28.0") (webkit "0.1"))
9 |
10 | ;; This file is free software; you can redistribute it and/or modify
11 | ;; it under the terms of the GNU General Public License as published
12 | ;; by the Free Software Foundation; either version 3, or (at your
13 | ;; option) any later version.
14 | ;;
15 | ;; This file is distributed in the hope that it will be useful,
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ;; GNU General Public License for more details.
19 | ;;
20 | ;; For a full copy of the GNU General Public License
21 | ;; see .
22 |
23 | ;;; Commentary:
24 | ;; See README.org
25 |
26 | ;;; Code:
27 |
28 | (require 'webkit)
29 |
30 | (declare-function webkit-add-style "webkit")
31 | (declare-function webkit-add-script "webkit")
32 | (declare-function webkit--file-to-string "webkit")
33 | (declare-function webkit--execute-js "webkit-module")
34 | (declare-function webkit--focus "webkit-module")
35 |
36 | (defconst webkit--base (file-name-directory load-file-name))
37 |
38 | (defvar webkit--id)
39 |
40 | (defvar webkit--hints-script (webkit--file-to-string
41 | (expand-file-name "hints.js" webkit--base)))
42 | (defvar webkit--hints-style (webkit--file-to-string
43 | (expand-file-name "hints.css" webkit--base)))
44 |
45 | (defcustom webkit-ace-chars "asdfghjklweio"
46 | "Link hint characters."
47 | :type 'string
48 | :group 'webkit)
49 |
50 | (defun webkit-ace--callback (msg)
51 | ;;(message "webkit-ace--callback %s" msg)
52 | (let ((length (string-to-number msg)))
53 | (when (> length 1)
54 | (webkit--execute-js webkit--id
55 | (format "__WKViewHints.update('%c');" (read-key))
56 | "webkit-ace--callback"))))
57 |
58 | (defun webkit-ace (&optional webkit-id)
59 | "Start a webkit ace jump."
60 | (interactive)
61 | (webkit--execute-js
62 | (or webkit-id webkit--id)
63 | (format "__WKViewHints.init('%s');" webkit-ace-chars)
64 | "webkit-ace--callback"))
65 |
66 | (defun webkit-ace-init ()
67 | (webkit-add-style webkit--hints-style)
68 | (webkit-add-script webkit--hints-script))
69 |
70 | (add-hook 'webkit-new-hook #'webkit-ace-init)
71 | (define-key webkit-mode-map "o" 'webkit-ace)
72 |
73 | (provide 'webkit-ace)
74 | ;;; webkit-ace.el ends here
75 |
--------------------------------------------------------------------------------
/webkit-dark.el:
--------------------------------------------------------------------------------
1 | ;;; webkit-dark.el --- simple dark mode for webkit dynamic module -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2020 Akira Kyle
4 |
5 | ;; Author: Akira Kyle
6 | ;; URL: https://github.com/akirakyle/emacs-webkit
7 | ;; Version: 0.1
8 | ;; Package-Requires: ((emacs "28.0") (webkit "0.1"))
9 |
10 | ;; This file is free software; you can redistribute it and/or modify
11 | ;; it under the terms of the GNU General Public License as published
12 | ;; by the Free Software Foundation; either version 3, or (at your
13 | ;; option) any later version.
14 | ;;
15 | ;; This file is distributed in the hope that it will be useful,
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ;; GNU General Public License for more details.
19 | ;;
20 | ;; For a full copy of the GNU General Public License
21 | ;; see .
22 |
23 | ;;; Commentary:
24 | ;; See README.org
25 |
26 | ;;; Code:
27 |
28 | (require 'webkit)
29 |
30 | (declare-function webkit-add-style "webkit")
31 |
32 | (defvar webkit--dark-style "
33 | html {
34 | -webkit-filter: hue-rotate(180deg) invert(90%) !important;
35 | }
36 |
37 | iframe,img,video {
38 | -webkit-filter: brightness(80%) invert(100%) hue-rotate(180deg) !important;
39 | }")
40 |
41 | (defcustom webkit-dark-mode nil
42 | "Turn on webkit dark mode globally."
43 | :type 'bool
44 | :group 'webkit)
45 |
46 | (defun webkit-dark-toggle ()
47 | (interactive)
48 | (if webkit-dark-mode
49 | (webkit-remove-style webkit--dark-style)
50 | (webkit-add-style webkit--dark-style))
51 | (setq-local webkit-dark-mode (not webkit-dark-mode)))
52 |
53 | (defun webkit-dark-init ()
54 | (when webkit-dark-mode
55 | (webkit-add-style webkit--dark-style)))
56 |
57 | (add-hook 'webkit-new-hook #'webkit-dark-init)
58 | (define-key webkit-mode-map (kbd "C-c d") 'webkit-dark-toggle)
59 |
60 | (provide 'webkit-dark)
61 | ;;; webkit-dark.el ends here
62 |
--------------------------------------------------------------------------------
/webkit-history.el:
--------------------------------------------------------------------------------
1 | ;;; webkit-history.el --- history for webkit dynamic module -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2020 Akira Kyle
4 |
5 | ;; Author: Akira Kyle
6 | ;; URL: https://github.com/akirakyle/emacs-webkit
7 | ;; Version: 0.1
8 | ;; Package-Requires: ((emacs "28.0") (webkit "0.1"))
9 |
10 | ;; This file is free software; you can redistribute it and/or modify
11 | ;; it under the terms of the GNU General Public License as published
12 | ;; by the Free Software Foundation; either version 3, or (at your
13 | ;; option) any later version.
14 | ;;
15 | ;; This file is distributed in the hope that it will be useful,
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ;; GNU General Public License for more details.
19 | ;;
20 | ;; For a full copy of the GNU General Public License
21 | ;; see .
22 |
23 | ;;; Commentary:
24 | ;; See README.org
25 |
26 | ;;; Code:
27 |
28 | (declare-function webkit--get-title "webkit-module")
29 | (declare-function webkit--get-uri "webkit-module")
30 |
31 | (defvar webkit--id)
32 |
33 | (defcustom webkit-history-file
34 | (expand-file-name "history" (locate-user-emacs-file "webkit/"))
35 | "File to store history of `webkit' sessions.
36 | Set to `nil' to disable saving history to a file (history will
37 | still be kept in memory)."
38 | :type 'file
39 | :group 'webkit)
40 |
41 | (defvar webkit-history-table nil)
42 |
43 | (cl-defstruct webkit-history-item uri title last-time (visit-count 1))
44 |
45 | (defun webkit-history-item-serialize (item)
46 | (list (webkit-history-item-uri item)
47 | (webkit-history-item-title item)
48 | (webkit-history-item-last-time item)))
49 |
50 | (defun webkit-history-item-deserialize (list)
51 | (make-webkit-history-item
52 | :uri (car list)
53 | :title (cadr list)
54 | :last-time (caddr list)))
55 |
56 | (defun webkit-history-completion-text (item)
57 | (let* ((title (webkit-history-item-title item))
58 | (uri (webkit-history-item-uri item))
59 | (text (concat title " (" uri ")")))
60 | (put-text-property (+ 2 (length title)) (1- (length text)) 'face 'link text)
61 | text))
62 |
63 | (defun webkit-history-completing-read (prompt)
64 | "Prompt for a URI using COMPLETING-READ from webkit history."
65 | (let ((completions ())
66 | (key-to-count (lambda (k) (webkit-history-item-visit-count
67 | (gethash (cdr k) webkit-history-table)))))
68 | (maphash (lambda (k v)
69 | (push (cons (webkit-history-completion-text v) k) completions))
70 | webkit-history-table)
71 | (setq completions (sort completions (lambda (k1 k2)
72 | (> (funcall key-to-count k1)
73 | (funcall key-to-count k2)))))
74 | (let* ((completion (completing-read prompt completions))
75 | (uri (cdr (assoc completion completions))))
76 | (if uri uri completion))))
77 |
78 | (defun webkit-history-add-item (item)
79 | (let* ((uri (webkit-history-item-uri item))
80 | (previous-item (gethash uri webkit-history-table)))
81 | (when previous-item
82 | (setf (webkit-history-item-visit-count item)
83 | (+ 1 (webkit-history-item-visit-count previous-item))))
84 | (puthash uri item webkit-history-table)))
85 |
86 | (defun webkit-history-add ()
87 | (let ((save-silently t)
88 | (new-item (make-webkit-history-item
89 | :title (webkit--get-title webkit--id)
90 | :uri (webkit--get-uri webkit--id)
91 | :last-time (time-convert (current-time) 'integer))))
92 | (unless (string= (webkit-history-item-uri new-item) "about:blank")
93 | (webkit-history-add-item new-item)
94 | (when webkit-history-file
95 | (append-to-file (format "%S\n" (webkit-history-item-serialize new-item))
96 | nil webkit-history-file)))))
97 |
98 | (defun webkit-history-load ()
99 | (with-current-buffer (find-file-noselect webkit-history-file)
100 | (goto-char (point-min))
101 | (condition-case nil
102 | (while t
103 | (webkit-history-add-item
104 | (webkit-history-item-deserialize (read (current-buffer)))))
105 | (end-of-file nil))
106 | (kill-buffer)))
107 |
108 | (defun webkit-history-initialize ()
109 | "Setup required data structure and load history from WEBKIT-HISTORY-FILE."
110 | (add-hook 'webkit-load-finished-hook #'webkit-history-add)
111 | (setq webkit-history-table (make-hash-table :test 'equal))
112 | (when webkit-history-file
113 | (webkit-history-load))
114 | nil)
115 |
116 | (webkit-history-initialize)
117 |
118 | (provide 'webkit-history)
119 | ;;; webkit-history.el ends here
120 |
--------------------------------------------------------------------------------
/webkit-module.c:
--------------------------------------------------------------------------------
1 | #define _POSIX_SOURCE 1
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "emacs-module.h"
12 |
13 | #ifdef DEBUG
14 | #define DEBUG_TEST 1
15 | #else
16 | #define DEBUG_TEST 0
17 | #endif
18 |
19 | #define debug_print(fmt, ...) \
20 | do { if (DEBUG_TEST) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
21 |
22 | int plugin_is_GPL_compatible;
23 |
24 | /* Frequently-used symbols. */
25 | static emacs_value Qnil;
26 | static emacs_value Qt;
27 |
28 | typedef struct Client {
29 | GtkWidget *container;
30 | WebKitWebView *view;
31 | int fd;
32 | } Client;
33 |
34 | typedef struct Callback {
35 | Client *c;
36 | char *id;
37 | } Callback;
38 |
39 | static bool
40 | copy_string_contents (emacs_env *env, emacs_value value,
41 | char **buffer, size_t *size)
42 | {
43 | ptrdiff_t buffer_size;
44 | if (!env->copy_string_contents (env, value, NULL, &buffer_size))
45 | return false;
46 | assert (env->non_local_exit_check (env) == emacs_funcall_exit_return);
47 | assert (buffer_size > 0);
48 | *buffer = malloc ((size_t) buffer_size);
49 | if (*buffer == NULL)
50 | {
51 | env->non_local_exit_signal (env, env->intern (env, "memory-full"),
52 | env->intern (env, "nil"));
53 | return false;
54 | }
55 | ptrdiff_t old_buffer_size = buffer_size;
56 | if (!env->copy_string_contents (env, value, *buffer, &buffer_size))
57 | {
58 | free (*buffer);
59 | *buffer = NULL;
60 | return false;
61 | }
62 | assert (env->non_local_exit_check (env) == emacs_funcall_exit_return);
63 | assert (buffer_size == old_buffer_size);
64 | *size = (size_t) (buffer_size - 1);
65 | return true;
66 | }
67 |
68 | Client *
69 | get_client (emacs_env *env, emacs_value value)
70 | {
71 | Client *c = (Client *)env->get_user_ptr(env, value);
72 | if ((env->non_local_exit_check(env) == emacs_funcall_exit_return)
73 | && c->container != NULL && c->view != NULL)
74 | return c;
75 | return NULL;
76 | }
77 |
78 | static emacs_value
79 | webkit_set_zoom (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
80 | {
81 | double zoom = env->extract_float (env, args[1]);
82 | Client *c = get_client (env, args[0]);
83 | if (c != NULL)
84 | webkit_web_view_set_zoom_level (c->view, (gdouble)zoom);
85 | return Qnil;
86 | }
87 |
88 | static emacs_value
89 | webkit_get_zoom (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
90 | {
91 | Client *c = get_client (env, args[0]);
92 | if (c != NULL)
93 | {
94 | gdouble zoom = webkit_web_view_get_zoom_level (c->view);
95 | return env->make_float (env, (double)zoom);
96 | }
97 | return Qnil;
98 | }
99 |
100 | static emacs_value
101 | webkit_forward (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
102 | {
103 | Client *c = get_client (env, args[0]);
104 | if (c != NULL)
105 | webkit_web_view_go_forward (c->view);
106 | return Qnil;
107 | }
108 |
109 | static emacs_value
110 | webkit_back (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
111 | {
112 | Client *c = get_client (env, args[0]);
113 | if (c != NULL)
114 | webkit_web_view_go_back (c->view);
115 | return Qnil;
116 | }
117 |
118 | static emacs_value
119 | webkit_reload (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
120 | {
121 | Client *c = get_client (env, args[0]);
122 | if (c != NULL)
123 | webkit_web_view_reload (c->view);
124 | return Qnil;
125 | }
126 |
127 | static emacs_value
128 | webkit_get_title (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
129 | {
130 | Client *c = get_client (env, args[0]);
131 | if (c != NULL)
132 | {
133 | const gchar *title = webkit_web_view_get_title (c->view);
134 | if (title != NULL)
135 | return env->make_string (env, title, strlen (title));
136 | }
137 | return Qnil;
138 | }
139 |
140 | static emacs_value
141 | webkit_get_uri (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
142 | {
143 | Client *c = get_client (env, args[0]);
144 | if (c != NULL)
145 | {
146 | const gchar *uri = webkit_uri_for_display (webkit_web_view_get_uri
147 | (c->view));
148 | return env->make_string (env, uri, strlen (uri));
149 | }
150 | return Qnil;
151 | }
152 |
153 | static emacs_value
154 | webkit_load_uri (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
155 | {
156 | Client *c = get_client (env, args[0]);
157 | size_t size;
158 | char *uri = NULL;
159 | if ((c != NULL) && copy_string_contents (env, args[1], &uri, &size))
160 | webkit_web_view_load_uri (c->view, uri);
161 |
162 | free (uri);
163 | return Qnil;
164 | }
165 |
166 | static emacs_value
167 | webkit_hide (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
168 | {
169 | Client *c = get_client (env, args[0]);
170 | if (c != NULL)
171 | gtk_widget_hide (GTK_WIDGET (c->view));
172 | return Qnil;
173 | }
174 |
175 | static emacs_value
176 | webkit_show (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
177 | {
178 | Client *c = get_client (env, args[0]);
179 | if (c != NULL)
180 | gtk_widget_show (GTK_WIDGET (c->view));
181 | return Qnil;
182 | }
183 |
184 | static emacs_value
185 | webkit_focus (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
186 | {
187 | Client *c = get_client (env, args[0]);
188 | if (c != NULL)
189 | {
190 | gtk_widget_set_can_focus (GTK_WIDGET (c->view), TRUE);
191 | gtk_widget_grab_focus (GTK_WIDGET (c->view));
192 | }
193 | return Qnil;
194 | }
195 |
196 | static emacs_value
197 | webkit_unfocus (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
198 | {
199 | Client *c = get_client (env, args[0]);
200 | if (c != NULL)
201 | {
202 | gtk_widget_set_can_focus (GTK_WIDGET (c->view), FALSE);
203 | gtk_widget_grab_focus (GTK_WIDGET (c->container));
204 | }
205 | return Qnil;
206 | }
207 |
208 | static emacs_value
209 | webkit_search (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
210 | {
211 | Client *c = get_client (env, args[0]);
212 | size_t size;
213 | char *text = NULL;
214 | if ((c != NULL) && copy_string_contents (env, args[1], &text, &size))
215 | webkit_find_controller_search (webkit_web_view_get_find_controller(c->view),
216 | text,
217 | ((n > 2) && env->is_not_nil (env, args[2])) ?
218 | WEBKIT_FIND_OPTIONS_NONE :
219 | WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE,
220 | G_MAXUINT);
221 |
222 | free (text);
223 | return Qnil;
224 | }
225 |
226 | static emacs_value
227 | webkit_search_finish (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
228 | {
229 | Client *c = get_client (env, args[0]);
230 | if (c != NULL)
231 | webkit_find_controller_search_finish
232 | (webkit_web_view_get_find_controller(c->view));
233 | return Qnil;
234 | }
235 |
236 | static emacs_value
237 | webkit_search_next (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
238 | {
239 | Client *c = get_client (env, args[0]);
240 | if (c != NULL)
241 | webkit_find_controller_search_next
242 | (webkit_web_view_get_find_controller(c->view));
243 | return Qnil;
244 | }
245 |
246 | static emacs_value
247 | webkit_search_previous (emacs_env *env, ptrdiff_t n,
248 | emacs_value *args, void *ptr)
249 | {
250 | Client *c = get_client (env, args[0]);
251 | if (c != NULL)
252 | webkit_find_controller_search_previous
253 | (webkit_web_view_get_find_controller(c->view));
254 | return Qnil;
255 | }
256 |
257 | static emacs_value
258 | webkit_start_web_inspector (emacs_env *env, ptrdiff_t n,
259 | emacs_value *args, void *ptr)
260 | {
261 | Client *c = get_client (env, args[0]);
262 | if (c != NULL)
263 | {
264 | WebKitSettings *settings = webkit_web_view_get_settings (c->view);
265 | g_object_set (G_OBJECT(settings), "enable-developer-extras", TRUE, NULL);
266 |
267 | WebKitWebInspector *inspector = webkit_web_view_get_inspector (c->view);
268 | webkit_web_inspector_show (inspector);
269 | }
270 | return Qnil;
271 | }
272 |
273 | static emacs_value
274 | webkit_enable_javascript (emacs_env *env, ptrdiff_t n,
275 | emacs_value *args, void *ptr)
276 | {
277 | Client *c = get_client (env, args[0]);
278 | if (c != NULL)
279 | {
280 | WebKitSettings *settings = webkit_web_view_get_settings (c->view);
281 | g_object_set (G_OBJECT(settings), "enable-javascript-markup",
282 | env->is_not_nil (env, args[1]) ? TRUE : FALSE, NULL);
283 | debug_print ("c %p enable-javascript-markup %d\n", c,
284 | env->is_not_nil (env, args[1]) ? TRUE : FALSE);
285 | }
286 | return Qnil;
287 | }
288 |
289 | static emacs_value
290 | webkit_cookie_set_persistent_storage (emacs_env *env, ptrdiff_t n,
291 | emacs_value *args, void *ptr)
292 | {
293 | Client *c = get_client (env, args[0]);
294 | size_t size;
295 | char *file = NULL;
296 | if ((c != NULL) && copy_string_contents (env, args[1], &file, &size))
297 | {
298 | WebKitWebContext *context = webkit_web_view_get_context (c->view);
299 | WebKitCookieManager *cm = webkit_web_context_get_cookie_manager (context);
300 | webkit_cookie_manager_set_persistent_storage
301 | (cm, file, WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
302 | debug_print ("c %p webkit_cookie_set_persistent_storage %s\n", c, file);
303 | }
304 | free (file);
305 | return Qnil;
306 | }
307 |
308 | static emacs_value
309 | webkit_proxy_set_uri (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
310 | {
311 | Client *c = get_client (env, args[0]);
312 | size_t size;
313 | char *proxy_uri = NULL;
314 | if ((c != NULL) && copy_string_contents (env, args[1], &proxy_uri, &size))
315 | {
316 | WebKitWebContext *context = webkit_web_view_get_context (c->view);
317 | WebKitNetworkProxySettings *proxy = webkit_network_proxy_settings_new
318 | (proxy_uri, NULL);
319 | webkit_web_context_set_network_proxy_settings
320 | (context, WEBKIT_NETWORK_PROXY_MODE_CUSTOM, proxy);
321 | webkit_network_proxy_settings_free (proxy);
322 | debug_print ("c %p webkit_proxy_set_uri %s\n", c, proxy_uri);
323 | }
324 | free (proxy_uri);
325 | return Qnil;
326 | }
327 |
328 | static emacs_value
329 | webkit_proxy_set_default (emacs_env *env, ptrdiff_t n,
330 | emacs_value *args, void *ptr)
331 | {
332 | Client *c = get_client (env, args[0]);
333 | if ((c != NULL))
334 | {
335 | WebKitWebContext *context = webkit_web_view_get_context (c->view);
336 | webkit_web_context_set_network_proxy_settings
337 | (context, WEBKIT_NETWORK_PROXY_MODE_DEFAULT, NULL);
338 | debug_print ("c %p webkit_proxy_set_default\n", c);
339 | }
340 | return Qnil;
341 | }
342 |
343 | static emacs_value
344 | webkit_resize (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
345 | {
346 | Client *c = get_client(env, args[0]);
347 | int x = env->extract_integer(env, args[1]);
348 | int y = env->extract_integer(env, args[2]);
349 | int w = env->extract_integer(env, args[3]);
350 | int h = env->extract_integer(env, args[4]);
351 |
352 | debug_print ("c %p resize (x:%d y:%d w:%d h:%d)\n", c, x, y, w, h);
353 | if ((env->non_local_exit_check(env) == emacs_funcall_exit_return)
354 | && (c != NULL))
355 | {
356 | if (GTK_IS_FIXED(c->container))
357 | gtk_fixed_move (GTK_FIXED (c->container), GTK_WIDGET(c->view), x, y);
358 | else if (GTK_IS_WINDOW(c->container))
359 | gtk_window_move (GTK_WINDOW (c->container), x, y);
360 | else
361 | assert (0);
362 | gtk_widget_set_size_request(GTK_WIDGET(c->view), w, h);
363 | }
364 | return Qnil;
365 | }
366 |
367 | static ssize_t
368 | rio_writen (int fd, void *usrbuf, size_t n)
369 | {
370 | size_t nleft = n;
371 | ssize_t nwritten;
372 | char *bufp = usrbuf;
373 |
374 | while (nleft > 0) {
375 | if ((nwritten = write (fd, bufp, nleft)) <= 0) {
376 | if (errno == EINTR) /* Interrupted by sig handler return */
377 | nwritten = 0; /* and call write() again */
378 | else
379 | return -1; /* errno set by write() */
380 | }
381 | nleft -= nwritten;
382 | bufp += nwritten;
383 | }
384 | return n;
385 | }
386 |
387 | static void
388 | send_to_lisp (Client *c, const char *id, const char *message)
389 | {
390 | if (id == NULL || message == NULL
391 | || rio_writen (c->fd, (void *)id, strlen (id)+1) < 0
392 | || rio_writen (c->fd, (void *)message, strlen (message)+1) < 0)
393 | g_warning ("Sending to fd: %d; id: %s; message: %s;", c->fd, id, message);
394 | }
395 |
396 | static void
397 | webkit_js_finished (GObject *web_view, GAsyncResult *result, gpointer arg)
398 | {
399 | GError *error = NULL;
400 | Callback *cb = (Callback *) arg;
401 |
402 | debug_print ("c %p js_finished with id: %s\n", cb->c, cb->id);
403 |
404 | WebKitJavascriptResult *js_result =
405 | webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW(web_view),
406 | result, &error);
407 |
408 | if (!js_result)
409 | {
410 | g_warning ("Error running javascript: %s", error->message);
411 | g_error_free (error);
412 | return;
413 | }
414 |
415 | JSCValue *value = webkit_javascript_result_get_js_value (js_result);
416 | gchar *json = jsc_value_to_json (value, 1);
417 | JSCException *exception =
418 | jsc_context_get_exception (jsc_value_get_context (value));
419 | if (exception)
420 | g_warning ("Error running javascript: %s",
421 | jsc_exception_get_message (exception));
422 | else
423 | send_to_lisp (cb->c, cb->id, json == NULL ? "null" : json);
424 |
425 | debug_print ("Script result: %s\n", json);
426 |
427 | g_free (json);
428 | free (cb->id);
429 | free (cb);
430 | webkit_javascript_result_unref (js_result);
431 | }
432 |
433 | static emacs_value
434 | webkit_execute_js (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
435 | {
436 | Client *c = get_client (env, args[0]);
437 | size_t size;
438 | char *script = NULL;
439 | char *id = NULL;
440 | if ((c != NULL) && copy_string_contents (env, args[1], &script, &size))
441 | {
442 | if ((n == 3) && copy_string_contents (env, args[2], &id, &size))
443 | {
444 | Callback *cb = malloc (sizeof (Callback));
445 | cb->c = c;
446 | cb->id = id;
447 | webkit_web_view_run_javascript (c->view, script, NULL,
448 | webkit_js_finished, (gpointer) cb);
449 | }
450 | else
451 | {
452 | webkit_web_view_run_javascript (c->view, script, NULL, NULL, NULL);
453 | }
454 | }
455 | debug_print ("c %p executing script: %s id: %s\n", c, script, id);
456 | free (script);
457 | return Qnil;
458 | }
459 |
460 | static emacs_value
461 | webkit_add_user_style (emacs_env *env, ptrdiff_t n,
462 | emacs_value *args, void *ptr)
463 | {
464 | Client *c = get_client (env, args[0]);
465 | size_t size;
466 | char *style = NULL;
467 | if ((c != NULL) && copy_string_contents (env, args[1], &style, &size))
468 | {
469 | WebKitUserContentManager *ucm =
470 | webkit_web_view_get_user_content_manager (c->view);
471 | WebKitUserStyleSheet *user_style = webkit_user_style_sheet_new
472 | (style,
473 | ((n > 3) && env->is_not_nil (env, args[3])) ?
474 | WEBKIT_USER_CONTENT_INJECT_TOP_FRAME :
475 | WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
476 | ((n > 2) && env->is_not_nil (env, args[2])) ?
477 | WEBKIT_USER_STYLE_LEVEL_AUTHOR : WEBKIT_USER_STYLE_LEVEL_USER,
478 | NULL, NULL);
479 | webkit_user_content_manager_add_style_sheet (ucm, user_style);
480 | webkit_user_style_sheet_unref (user_style);
481 | }
482 | debug_print ("c %p add_user_style: %s\n", c, style);
483 | free (style);
484 | return Qnil;
485 | }
486 |
487 | static emacs_value
488 | webkit_remove_all_user_styles (emacs_env *env, ptrdiff_t n,
489 | emacs_value *args, void *ptr)
490 | {
491 | Client *c = get_client (env, args[0]);
492 | if (c != NULL)
493 | {
494 | WebKitUserContentManager *ucm =
495 | webkit_web_view_get_user_content_manager (c->view);
496 | webkit_user_content_manager_remove_all_style_sheets (ucm);
497 | }
498 | debug_print ("c %p webkit_remove_all_user_styles\n", c);
499 | return Qnil;
500 | }
501 | static emacs_value
502 | webkit_add_user_script (emacs_env *env, ptrdiff_t n,
503 | emacs_value *args, void *ptr)
504 | {
505 | Client *c = get_client (env, args[0]);
506 | size_t size;
507 | char *script = NULL;
508 | if ((c != NULL) && copy_string_contents (env, args[1], &script, &size))
509 | {
510 | WebKitUserContentManager *ucm =
511 | webkit_web_view_get_user_content_manager (c->view);
512 | WebKitUserScript *user_script =
513 | webkit_user_script_new (script,
514 | ((n > 3) && env->is_not_nil (env, args[3])) ?
515 | WEBKIT_USER_CONTENT_INJECT_TOP_FRAME :
516 | WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
517 | ((n > 2) && env->is_not_nil (env, args[2])) ?
518 | WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END :
519 | WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
520 | NULL, NULL);
521 | webkit_user_content_manager_add_script (ucm, user_script);
522 | webkit_user_script_unref (user_script);
523 | }
524 | debug_print ("c %p add_user_script: %s\n", c, script);
525 | free (script);
526 | return Qnil;
527 | }
528 |
529 | static emacs_value
530 | webkit_remove_all_user_scripts (emacs_env *env, ptrdiff_t n,
531 | emacs_value *args, void *ptr)
532 | {
533 | Client *c = get_client (env, args[0]);
534 | if (c != NULL)
535 | {
536 | WebKitUserContentManager *ucm =
537 | webkit_web_view_get_user_content_manager (c->view);
538 | webkit_user_content_manager_remove_all_scripts (ucm);
539 | }
540 | debug_print ("c %p webkit_remove_all_user_scripts\n", c);
541 | return Qnil;
542 | }
543 |
544 | static void
545 | webkit_script_message_cb (WebKitUserContentManager *scriptor,
546 | WebKitJavascriptResult *js_result, gpointer data)
547 | {
548 | Client *c = (Client *)data;
549 | WebKitUserContentManager *ucm =
550 | webkit_web_view_get_user_content_manager (c->view);
551 | GSignalInvocationHint *hint = g_signal_get_invocation_hint ((gpointer)ucm);
552 | const gchar *name = g_quark_to_string (hint->detail);
553 |
554 | JSCValue *value = webkit_javascript_result_get_js_value (js_result);
555 | gchar *json = jsc_value_to_json (value, 1);
556 | JSCException *exception =
557 | jsc_context_get_exception (jsc_value_get_context (value));
558 | if (exception)
559 | g_warning ("Error in javascript message recieve: %s",
560 | jsc_exception_get_message (exception));
561 | else
562 | send_to_lisp (c, name, json == NULL ? "null" : json);
563 |
564 | debug_print ("Script name: %s, result: %s\n", name, json);
565 | }
566 |
567 | static emacs_value
568 | webkit_register_script_message (emacs_env *env, ptrdiff_t n,
569 | emacs_value *args, void *ptr)
570 | {
571 | Client *c = get_client (env, args[0]);
572 | size_t size;
573 | char *name = NULL;
574 | if ((c != NULL) && copy_string_contents (env, args[1], &name, &size))
575 | {
576 | WebKitUserContentManager *ucm =
577 | webkit_web_view_get_user_content_manager (c->view);
578 |
579 | gchar *signal_name = g_strconcat ("script-message-received::", name, NULL);
580 | g_signal_connect (ucm, signal_name,
581 | G_CALLBACK (webkit_script_message_cb), c);
582 | g_free (signal_name);
583 |
584 | if (!webkit_user_content_manager_register_script_message_handler (ucm, name))
585 | g_signal_handlers_disconnect_matched
586 | (ucm, G_SIGNAL_MATCH_FUNC, 0, g_quark_from_string (name), 0,
587 | G_CALLBACK (webkit_script_message_cb), 0);
588 | }
589 | debug_print ("c %p register_script_message: %s\n", c, name);
590 | free (name);
591 | return Qnil;
592 | }
593 |
594 | static emacs_value
595 | webkit_unregister_script_message (emacs_env *env, ptrdiff_t n,
596 | emacs_value *args, void *ptr)
597 | {
598 | size_t size;
599 | char *name = NULL;
600 | Client *c = get_client (env, args[0]);
601 | if ((c != NULL) && copy_string_contents (env, args[1], &name, &size))
602 | {
603 | WebKitUserContentManager *ucm =
604 | webkit_web_view_get_user_content_manager (c->view);
605 |
606 | webkit_user_content_manager_unregister_script_message_handler (ucm, name);
607 | g_signal_handlers_disconnect_matched
608 | (ucm, G_SIGNAL_MATCH_FUNC, 0, g_quark_from_string (name), 0,
609 | G_CALLBACK (webkit_script_message_cb), 0);
610 | }
611 | debug_print ("c %p unregister_script_message: %s\n", c, name);
612 | free (name);
613 | return Qnil;
614 | }
615 |
616 | static gboolean
617 | webview_key_press_event (GtkWidget *w, GdkEvent *e, Client *c)
618 | {
619 | debug_print ("key_press_event: %p\n", c);
620 | switch (e->type) {
621 | case GDK_KEY_PRESS:
622 | debug_print ("key.keyval = %d\n", e->key.keyval);
623 | //if (e->key.keyval == GDK_KEY_Escape && e->key.state == 0)
624 | if ((e->key.state & GDK_CONTROL_MASK) && (e->key.keyval == 'g'))
625 | {
626 | debug_print ("c %p webview_key_press_event c-g detected\n", c);
627 | send_to_lisp (c, "webkit--callback-unfocus", "");
628 | return TRUE;
629 | }
630 | default:
631 | break;
632 | }
633 | return FALSE;
634 | }
635 |
636 | static void
637 | webview_load_changed (WebKitWebView *webview, WebKitLoadEvent load_event,
638 | Client *c)
639 | {
640 | switch (load_event) {
641 | case WEBKIT_LOAD_STARTED:
642 | break;
643 | case WEBKIT_LOAD_REDIRECTED:
644 | break;
645 | case WEBKIT_LOAD_COMMITTED:
646 | break;
647 | case WEBKIT_LOAD_FINISHED:
648 | send_to_lisp (c, "webkit--load-finished", "");
649 | break;
650 | }
651 | }
652 |
653 | static void
654 | webview_notify_load_progress (WebKitWebView *webview, GParamSpec *pspec, Client *c)
655 | {
656 | gdouble prog = 100.0 * webkit_web_view_get_estimated_load_progress (webview);
657 | gchar *buf = g_strdup_printf ("%f", prog);
658 | send_to_lisp (c, "webkit--callback-progress", buf);
659 | g_free (buf);
660 | }
661 |
662 | static void
663 | webview_notify_uri (WebKitWebView *webview, GParamSpec *pspec, Client *c)
664 | {
665 | const gchar *uri = webkit_uri_for_display (webkit_web_view_get_uri (webview));
666 | if (uri != NULL)
667 | send_to_lisp (c, "webkit--callback-uri", uri);
668 | }
669 |
670 | static void
671 | webview_notify_title (WebKitWebView *webview, GParamSpec *pspec, Client *c)
672 | {
673 | const gchar *title = webkit_web_view_get_title(webview);
674 |
675 | if (title != NULL)
676 | send_to_lisp (c, "webkit--callback-title", title);
677 | }
678 |
679 | static void
680 | findcontroller_counted_matches(WebKitFindController *finder,
681 | guint count, Client *c)
682 | {
683 | gchar *buf = g_strdup_printf ("%d", count);
684 | send_to_lisp (c, "webkit--counted-matches", buf);
685 | g_free (buf);
686 | }
687 |
688 | static void
689 | decide_navigation_action (Client *c, WebKitPolicyDecision *dec)
690 | {
691 | WebKitNavigationAction *action =
692 | webkit_navigation_policy_decision_get_navigation_action
693 | (WEBKIT_NAVIGATION_POLICY_DECISION (dec));
694 | WebKitURIRequest *req = webkit_navigation_action_get_request(action);
695 | const char *uri = webkit_uri_request_get_uri(req);
696 | guint button = webkit_navigation_action_get_mouse_button (action);
697 | guint mod = webkit_navigation_action_get_modifiers (action);
698 |
699 | /* Request new view if triggered by CTRL-LeftMouse or MiddleMouse. */
700 | if ((webkit_navigation_action_get_navigation_type(action) ==
701 | WEBKIT_NAVIGATION_TYPE_LINK_CLICKED)
702 | && (button == 2 || (button == 1 && mod & GDK_CONTROL_MASK)))
703 | {
704 | webkit_policy_decision_ignore(dec);
705 | send_to_lisp (c, "webkit--callback-new-view", uri);
706 | }
707 | else
708 | {
709 | webkit_policy_decision_use(dec);
710 | }
711 | }
712 |
713 | static void
714 | decide_new_window_action (Client *c, WebKitPolicyDecision *dec)
715 | {
716 | WebKitNavigationAction *action =
717 | webkit_navigation_policy_decision_get_navigation_action
718 | (WEBKIT_NAVIGATION_POLICY_DECISION (dec));
719 | WebKitURIRequest *req = webkit_navigation_action_get_request(action);
720 | const char *uri = webkit_uri_request_get_uri(req);
721 |
722 | /* This is triggered on link click for links with target="_blank" */
723 | if (webkit_navigation_action_is_user_gesture(action))
724 | send_to_lisp (c, "webkit--callback-new-view", uri);
725 | webkit_policy_decision_ignore(dec);
726 | }
727 |
728 | static void
729 | decide_response (Client *c, WebKitPolicyDecision *dec)
730 | {
731 | if (webkit_response_policy_decision_is_mime_type_supported
732 | (WEBKIT_RESPONSE_POLICY_DECISION(dec))) {
733 | webkit_policy_decision_use(dec);
734 | } else {
735 | webkit_policy_decision_download(dec);
736 | }
737 | }
738 |
739 | static gboolean
740 | webview_decide_policy (WebKitWebView *webview, WebKitPolicyDecision *dec,
741 | WebKitPolicyDecisionType type, Client *c)
742 | {
743 | switch (type)
744 | {
745 | case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
746 | decide_navigation_action (c, dec);
747 | break;
748 |
749 | case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
750 | decide_new_window_action (c, dec);
751 | break;
752 |
753 | case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
754 | decide_response (c, dec);
755 | break;
756 |
757 | default:
758 | webkit_policy_decision_use (dec);
759 | break;
760 | }
761 | return TRUE;
762 | }
763 |
764 | static void
765 | webcontext_download_started (WebKitWebContext *webctx, WebKitDownload *download,
766 | Client *c)
767 | {
768 | const char *uri =
769 | webkit_uri_request_get_uri(webkit_download_get_request(download));
770 | webkit_download_cancel(download);
771 |
772 | if (uri != NULL)
773 | send_to_lisp (c, "webkit--callback-download-request", uri);
774 | }
775 |
776 | /*
777 | #ifdef DEBUG
778 | static void
779 | print_widget_tree (GList *widgets)
780 | {
781 | for (GList *l = widgets; l != NULL; l = l->next)
782 | {
783 | char *path = gtk_widget_path_to_string (gtk_widget_get_path (l->data));
784 | const char *type_name = G_OBJECT_TYPE_NAME (l->data);
785 | //debug_print ("widget %p; fixed %d; path %s\n", l->data,
786 | // GTK_IS_FIXED (l->data), path);
787 | if (GTK_IS_MENU(l->data))
788 | continue;
789 | debug_print ("widget %p; fixed %d; type %s\n", l->data,
790 | GTK_IS_FIXED (l->data), type_name);
791 | if (GTK_IS_WINDOW (l->data))
792 | debug_print (" ->window; focused %d; title %s\n",
793 | gtk_window_has_toplevel_focus (l->data),
794 | gtk_window_get_title (l->data));
795 | if (GTK_IS_CONTAINER(l->data))
796 | print_widget_tree (gtk_container_get_children (GTK_CONTAINER (l->data)));
797 | }
798 | }
799 | #endif
800 | */
801 |
802 | static GtkFixed *
803 | find_fixed_widget (GList *widgets)
804 | {
805 | for (GList *l = widgets; l != NULL; l = l->next)
806 | {
807 | debug_print ("widget %p; fixed %d; type %s\n", l->data,
808 | GTK_IS_FIXED (l->data), G_OBJECT_TYPE_NAME (l->data));
809 | if (GTK_IS_FIXED (l->data))
810 | return l->data;
811 | if (GTK_IS_BOX (l->data))
812 | {
813 | GtkFixed *fixed = find_fixed_widget (gtk_container_get_children
814 | (GTK_CONTAINER (l->data)));
815 | if (fixed != NULL)
816 | return fixed;
817 | }
818 | }
819 | return NULL;
820 | }
821 |
822 | static GtkFixed *
823 | find_focused_fixed_widget ()
824 | {
825 | GList *widgets = gtk_window_list_toplevels ();
826 | for (GList *l = widgets; l != NULL; l = l->next)
827 | {
828 | debug_print ("window %p focused %d\n", l->data,
829 | gtk_window_has_toplevel_focus (l->data));
830 | if (gtk_window_has_toplevel_focus (l->data))
831 | return find_fixed_widget (gtk_container_get_children
832 | (GTK_CONTAINER (l->data)));
833 | }
834 | return NULL;
835 | }
836 |
837 | int container_child_prop_helper (GtkWidget *container, gpointer child,
838 | const char *prop)
839 | {
840 | GValue v = G_VALUE_INIT;
841 | g_value_init (&v, G_TYPE_INT);
842 | gtk_container_child_get_property (GTK_CONTAINER (container),
843 | GTK_WIDGET (child), prop, &v);
844 | return g_value_get_int (&v);
845 | }
846 |
847 | static void
848 | webview_change_container (Client *c, GtkFixed *fixed)
849 | {
850 | debug_print ("c %p change_container from %p to %p\n", c, c->container, fixed);
851 | if (c->container != NULL)
852 | gtk_container_remove (GTK_CONTAINER (c->container),
853 | GTK_WIDGET (c->view));
854 | c->container = GTK_WIDGET (fixed);
855 |
856 | /* play nice with child frames (should webkit always go under child frames?) */
857 | GList *widgets = gtk_container_get_children (GTK_CONTAINER (c->container));
858 |
859 | gtk_fixed_put (GTK_FIXED (c->container), GTK_WIDGET (c->view), 0, 0);
860 | //gtk_container_add (GTK_CONTAINER (c->container), GTK_WIDGET (c->view));
861 |
862 | for (GList *l = widgets; l != NULL; l = l->next)
863 | {
864 | int x = container_child_prop_helper (c->container, l->data, "x");
865 | int y = container_child_prop_helper (c->container, l->data, "y");
866 | debug_print ("c %p removing child with x: %d, y: %d\n", c, x, y);
867 | g_object_ref (l->data);
868 | gtk_container_remove (GTK_CONTAINER (c->container), GTK_WIDGET (l->data));
869 | gtk_fixed_put (GTK_FIXED (c->container), GTK_WIDGET (l->data), x, y);
870 | g_object_unref (l->data);
871 | }
872 | }
873 |
874 | static emacs_value
875 | webkit_xid_to_pointer (emacs_env *env, ptrdiff_t n,
876 | emacs_value *args, void *ptr)
877 | {
878 | intmax_t xid = env->extract_integer(env, args[0]);
879 | debug_print ("xid_to_pointer xid %lu\n", xid);
880 |
881 | GList *widgets = gtk_window_list_toplevels ();
882 | for (GList *l = widgets; l != NULL; l = l->next)
883 | {
884 | debug_print ("window %p focused %d\n", l->data,
885 | gtk_window_has_toplevel_focus (l->data));
886 | GtkFixed *fixed = find_fixed_widget (gtk_container_get_children
887 | (GTK_CONTAINER (l->data)));
888 | if (fixed != NULL)
889 | {
890 | //debug_print ("gdk window %p\n", gtk_widget_get_window (GTK_WIDGET(fixed)));
891 | //debug_print ("gdk window %p\n", gtk_widget_get_window (l->data));
892 | uintptr_t fixed_xid = (uintptr_t)GDK_WINDOW_XID
893 | (gtk_widget_get_window (GTK_WIDGET(fixed)));
894 | debug_print ("fixed %p xid %lu\n", fixed, fixed_xid);
895 |
896 | if (((uintptr_t)xid) == fixed_xid)
897 | return env->make_integer (env, (intmax_t)l->data);
898 | }
899 | }
900 | return Qnil;
901 | }
902 |
903 | static emacs_value
904 | webkit_move_to_frame (emacs_env *env, ptrdiff_t n,
905 | emacs_value *args, void *ptr)
906 | {
907 | intmax_t window_id = env->extract_integer(env, args[1]);
908 | Client *c = get_client (env, args[0]);
909 | #ifdef DEBUG
910 | //print_widget_tree (gtk_window_list_toplevels());
911 | #endif
912 | debug_print ("c %p move_to_frame %p\n", c, (void *)window_id);
913 | if (c != NULL)
914 | {
915 | //webkit_move_to_focused_frame_internal (c, env);
916 | GList *widgets = gtk_window_list_toplevels ();
917 | for (GList *l = widgets; l != NULL; l = l->next)
918 | {
919 | debug_print ("window %p focused %d\n", l->data,
920 | gtk_window_has_toplevel_focus (l->data));
921 | if (l->data == (void *)window_id)
922 | {
923 | GtkFixed *fixed = find_fixed_widget (gtk_container_get_children
924 | (GTK_CONTAINER (l->data)));
925 | if (fixed != NULL)
926 | {
927 | webview_change_container (c, fixed);
928 | return Qnil;
929 | }
930 | }
931 | }
932 | }
933 | env->non_local_exit_signal
934 | (env, env->intern (env, "webkit-module-no-fixed-widget"),
935 | env->intern (env, "nil"));
936 | return Qnil;
937 | }
938 |
939 | static gboolean
940 | webview_close (WebKitWebView *webview, Client *c)
941 | {
942 | debug_print ("c %p webview_close\n", c);
943 | assert (webview == c->view);
944 | send_to_lisp (c, "webkit--close", "");
945 | return TRUE;
946 | }
947 |
948 | static void
949 | window_destroy (GtkWidget *window, Client *c)
950 | {
951 | debug_print ("c %p window_destroy\n", c);
952 | if (c->container != NULL)
953 | gtk_widget_destroy (c->container);
954 | c->container = NULL;
955 | send_to_lisp (c, "webkit--close", "");
956 | }
957 |
958 | /*
959 | static void
960 | webview_destroy (WebKitWebView *webview, Client *c)
961 | {
962 | debug_print ("webview_destroy %p\n", c);
963 | //c->container = NULL;
964 | #ifdef DEBUG
965 | print_widget_tree (gtk_window_list_toplevels());
966 | #endif
967 | GtkFixed *fixed = find_fixed_widget (gtk_window_list_toplevels ());
968 | if (fixed == NULL)
969 | {
970 | c->container = NULL;
971 | send_to_lisp (c, "webkit--close", "");
972 | }
973 | else
974 | {
975 | webkit_move_to_frame (c, fixed);
976 | }
977 | }
978 | */
979 |
980 | static emacs_value
981 | webkit_destroy (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
982 | {
983 | Client *c = get_client (env, args[0]);
984 | debug_print ("c %p webkit_destroy\n", c);
985 | if (c != NULL)
986 | {
987 | if (GTK_IS_WINDOW(c->container))
988 | gtk_widget_destroy (c->container);
989 |
990 | c->container = NULL;
991 | }
992 | return Qnil;
993 | }
994 |
995 | static void
996 | client_free (void *ptr)
997 | {
998 | debug_print ("c %p client_free\n", ptr);
999 | Client *c = (Client *)ptr;
1000 | assert (c->container == NULL);
1001 | gtk_widget_destroy (GTK_WIDGET (c->view));
1002 | g_object_unref (c->view);
1003 | free(c);
1004 | }
1005 |
1006 | static emacs_value
1007 | webkit_new (emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
1008 | {
1009 | int argc = 0;
1010 | char **argv = NULL;
1011 |
1012 | if (!gtk_init_check (&argc, &argv))
1013 | {
1014 | env->non_local_exit_signal
1015 | (env, env->intern (env, "webkit-module-init-gtk-failed"),
1016 | env->intern (env, "nil"));
1017 | return Qnil;
1018 | }
1019 |
1020 | Client *c;
1021 | if (!(c = calloc (1, sizeof (Client))))
1022 | {
1023 | env->non_local_exit_signal (env, env->intern (env, "memory-full"),
1024 | env->intern (env, "nil"));
1025 | return Qnil;
1026 | }
1027 |
1028 | c->fd = env->open_channel (env, args[0]);
1029 | if (env->non_local_exit_check(env) != emacs_funcall_exit_return)
1030 | return Qnil;
1031 |
1032 | WebKitWebContext *context = webkit_web_context_new ();
1033 | //webkit_web_context_set_sandbox_enabled (context, true);
1034 |
1035 | c->view = WEBKIT_WEB_VIEW (webkit_web_view_new_with_context (context));
1036 | /* set lifetime of c->view to be same as c which is owend by Emacs user_ptr */
1037 | g_object_ref (c->view);
1038 | g_object_ref_sink (c->view);
1039 | gtk_widget_set_can_focus(GTK_WIDGET(c->view), FALSE);
1040 | //gtk_widget_set_focus_on_click (GTK_WIDGET (c->view), FALSE);
1041 |
1042 | #ifdef DEBUG
1043 | //print_widget_tree (gtk_window_list_toplevels());
1044 | #endif
1045 | if (env->is_not_nil (env, args[1]))
1046 | {
1047 | c->container = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1048 | //gtk_window_set_default_size
1049 | // (GTK_WINDOW(c->container),
1050 | // (n > 2) ? env->extract_integer (env, args[2]) : 400
1051 | // (n > 3) ? env->extract_integer (env, args[3]) : 600);
1052 |
1053 | g_signal_connect (G_OBJECT (c->container), "destroy",
1054 | G_CALLBACK(window_destroy), c);
1055 |
1056 | gtk_container_add (GTK_CONTAINER(c->container), GTK_WIDGET (c->view));
1057 | gtk_widget_show_all(c->container);
1058 | }
1059 | else
1060 | {
1061 | GtkFixed *fixed = find_focused_fixed_widget ();
1062 | if (fixed == NULL)
1063 | {
1064 | const char *err_msg = "webkit-module-no-focused-fixed-widget";
1065 | env->non_local_exit_signal
1066 | (env, env->intern (env, err_msg),
1067 | env->make_string (env, err_msg, strlen (err_msg)));
1068 | return Qnil;
1069 | }
1070 | webview_change_container (c, fixed);
1071 | gtk_widget_show_all (GTK_WIDGET (c->view));
1072 | }
1073 |
1074 | //g_signal_connect (G_OBJECT (c->view), "destroy",
1075 | // G_CALLBACK(webview_destroy), c);
1076 | g_signal_connect (G_OBJECT (c->view), "close",
1077 | G_CALLBACK(webview_close), c);
1078 | g_signal_connect (G_OBJECT (c->view), "key-press-event",
1079 | G_CALLBACK (webview_key_press_event), c);
1080 | g_signal_connect (G_OBJECT (c->view), "load-changed",
1081 | G_CALLBACK (webview_load_changed), c);
1082 | g_signal_connect (G_OBJECT (c->view), "notify::title",
1083 | G_CALLBACK (webview_notify_title), c);
1084 | g_signal_connect (G_OBJECT (c->view), "notify::uri",
1085 | G_CALLBACK (webview_notify_uri), c);
1086 | g_signal_connect (G_OBJECT (c->view), "notify::estimated-load-progress",
1087 | G_CALLBACK (webview_notify_load_progress), c);
1088 | g_signal_connect (G_OBJECT (c->view), "decide-policy",
1089 | G_CALLBACK (webview_decide_policy), c);
1090 | g_signal_connect (webkit_web_view_get_context (c->view), "download-started",
1091 | G_CALLBACK (webcontext_download_started), c);
1092 | g_signal_connect (webkit_web_view_get_find_controller (c->view),
1093 | "counted-matches",
1094 | G_CALLBACK(findcontroller_counted_matches), c);
1095 |
1096 | #ifdef DEBUG
1097 | webkit_settings_set_enable_write_console_messages_to_stdout
1098 | (webkit_web_view_get_settings (c->view), true);
1099 | #endif
1100 | // https://bugs.webkit.org/show_bug.cgi?id=200856
1101 | // https://github.com/NixOS/nixpkgs/pull/103728
1102 | webkit_settings_set_hardware_acceleration_policy
1103 | (webkit_web_view_get_settings (c->view), WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER);
1104 | /* webkit uses GSubprocess which sets sigaction causing emacs to not catch
1105 | SIGCHLD with it's usual handle setup in catch_child_signal().
1106 | This resets the SIGCHLD sigaction */
1107 | struct sigaction old_action;
1108 | sigaction (SIGCHLD, NULL, &old_action);
1109 | webkit_web_view_load_uri(c->view, "about:blank");
1110 | sigaction (SIGCHLD, &old_action, NULL);
1111 |
1112 | return env->make_user_ptr (env, client_free, (void *) c);
1113 | }
1114 |
1115 | static void
1116 | mkfn (emacs_env *env,
1117 | ptrdiff_t min_arity,
1118 | ptrdiff_t max_arity,
1119 | emacs_value (*func) (emacs_env *env,
1120 | ptrdiff_t nargs,
1121 | emacs_value* args,
1122 | void *data),
1123 | const char *name,
1124 | const char *docstring,
1125 | void *data)
1126 | {
1127 | emacs_value Sfun = env->make_function (env, min_arity, max_arity,
1128 | func, docstring, data);
1129 | emacs_value Qfset = env->intern (env, "fset");
1130 | emacs_value Qsym = env->intern (env, name);
1131 |
1132 | env->funcall (env, Qfset, 2, (emacs_value[]){Qsym, Sfun});
1133 | }
1134 |
1135 | int
1136 | emacs_module_init (struct emacs_runtime *ert)
1137 | {
1138 | emacs_env *env = ert->get_environment (ert);
1139 | // Symbols
1140 | Qt = env->make_global_ref (env, env->intern(env, "t"));
1141 | Qnil = env->make_global_ref (env, env->intern(env, "nil"));
1142 |
1143 | // Functions
1144 | mkfn (env, 2, 2, webkit_new, "webkit--new", "", NULL);
1145 | mkfn (env, 1, 1, webkit_destroy, "webkit--destroy", "", NULL);
1146 | mkfn (env, 5, 5, webkit_resize, "webkit--resize", "", NULL);
1147 | mkfn (env, 2, 2, webkit_move_to_frame, "webkit--move-to-frame", "", NULL);
1148 | mkfn (env, 1, 1, webkit_xid_to_pointer, "webkit--xid-to-pointer", "", NULL);
1149 | mkfn (env, 1, 1, webkit_hide, "webkit--hide", "", NULL);
1150 | mkfn (env, 1, 1, webkit_show, "webkit--show", "", NULL);
1151 | mkfn (env, 1, 1, webkit_focus, "webkit--focus", "", NULL);
1152 | mkfn (env, 1, 1, webkit_unfocus, "webkit--unfocus", "", NULL);
1153 | mkfn (env, 1, 1, webkit_forward, "webkit--forward", "", NULL);
1154 | mkfn (env, 1, 1, webkit_back, "webkit--back", "", NULL);
1155 | mkfn (env, 1, 1, webkit_reload, "webkit--reload", "", NULL);
1156 | mkfn (env, 1, 1, webkit_get_zoom, "webkit--get-zoom", "", NULL);
1157 | mkfn (env, 2, 2, webkit_set_zoom, "webkit--set-zoom", "", NULL);
1158 | mkfn (env, 1, 1, webkit_get_title, "webkit--get-title", "", NULL);
1159 | mkfn (env, 1, 1, webkit_get_uri, "webkit--get-uri", "", NULL);
1160 | mkfn (env, 2, 2, webkit_load_uri, "webkit--load-uri", "", NULL);
1161 | mkfn (env, 2, 3, webkit_search, "webkit--search", "", NULL);
1162 | mkfn (env, 1, 1, webkit_search_finish, "webkit--search-finish", "", NULL);
1163 | mkfn (env, 1, 1, webkit_search_next, "webkit--search-next", "", NULL);
1164 | mkfn (env, 1, 1, webkit_search_previous, "webkit--search-previous", "", NULL);
1165 | mkfn (env, 1, 1, webkit_start_web_inspector, "webkit--start-web-inspector", "", NULL);
1166 | mkfn (env, 2, 2, webkit_enable_javascript, "webkit--enable-javascript", "", NULL);
1167 | mkfn (env, 2, 2, webkit_cookie_set_persistent_storage, "webkit--cookie-set-storage", "", NULL);
1168 | mkfn (env, 2, 2, webkit_proxy_set_uri, "webkit--proxy-set-uri", "", NULL);
1169 | mkfn (env, 1, 1, webkit_proxy_set_default, "webkit--proxy-set-default", "", NULL);
1170 | mkfn (env, 2, 3, webkit_execute_js, "webkit--execute-js", "", NULL);
1171 | mkfn (env, 2, 4, webkit_add_user_style, "webkit--add-user-style", "", NULL);
1172 | mkfn (env, 1, 1, webkit_remove_all_user_styles, "webkit--remove-all-user-styles", "", NULL);
1173 | mkfn (env, 2, 4, webkit_add_user_script, "webkit--add-user-script", "", NULL);
1174 | mkfn (env, 1, 1, webkit_remove_all_user_scripts, "webkit--remove-all-user-scripts", "", NULL);
1175 | mkfn (env, 2, 2, webkit_register_script_message, "webkit--register-script-message", "", NULL);
1176 | mkfn (env, 2, 2, webkit_unregister_script_message, "webkit--unregister-script-message", "", NULL);
1177 |
1178 | emacs_value Qfeat = env->intern (env, "webkit-module");
1179 | emacs_value Qprovide = env->intern (env, "provide");
1180 | env->funcall (env, Qprovide, 1, (emacs_value[]){Qfeat});
1181 | debug_print ("init webkit-module\n");
1182 | return 0;
1183 | }
1184 |
1185 |
--------------------------------------------------------------------------------
/webkit.el:
--------------------------------------------------------------------------------
1 | ;;; webkit.el --- webkit dynamic module -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2020 Akira Kyle
4 |
5 | ;; Author: Akira Kyle
6 | ;; URL: https://github.com/akirakyle/emacs-webkit
7 | ;; Version: 0.1
8 | ;; Package-Requires: ((emacs "28.0"))
9 |
10 | ;; This file is free software; you can redistribute it and/or modify
11 | ;; it under the terms of the GNU General Public License as published
12 | ;; by the Free Software Foundation; either version 3, or (at your
13 | ;; option) any later version.
14 | ;;
15 | ;; This file is distributed in the hope that it will be useful,
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ;; GNU General Public License for more details.
19 | ;;
20 | ;; For a full copy of the GNU General Public License
21 | ;; see .
22 |
23 | ;;; Commentary:
24 | ;; See README.org
25 |
26 | ;;; Code:
27 |
28 | ;; Don't require dynamic module at byte compile time.
29 | ;; Generate this list with:
30 | ;; awk -F\" '/mkfn*/ {print "(declare-function", $2, "\"webkit-module\")"}' webkit-module.c
31 | (declare-function webkit--new "webkit-module")
32 | (declare-function webkit--destroy "webkit-module")
33 | (declare-function webkit--resize "webkit-module")
34 | (declare-function webkit--move-to-frame "webkit-module")
35 | (declare-function webkit--xid-to-pointer "webkit-module")
36 | (declare-function webkit--hide "webkit-module")
37 | (declare-function webkit--show "webkit-module")
38 | (declare-function webkit--focus "webkit-module")
39 | (declare-function webkit--unfocus "webkit-module")
40 | (declare-function webkit--forward "webkit-module")
41 | (declare-function webkit--back "webkit-module")
42 | (declare-function webkit--reload "webkit-module")
43 | (declare-function webkit--get-zoom "webkit-module")
44 | (declare-function webkit--set-zoom "webkit-module")
45 | (declare-function webkit--get-title "webkit-module")
46 | (declare-function webkit--get-uri "webkit-module")
47 | (declare-function webkit--load-uri "webkit-module")
48 | (declare-function webkit--search "webkit-module")
49 | (declare-function webkit--search-finish "webkit-module")
50 | (declare-function webkit--search-next "webkit-module")
51 | (declare-function webkit--search-previous "webkit-module")
52 | (declare-function webkit--start-web-inspector "webkit-module")
53 | (declare-function webkit--enable-javascript "webkit-module")
54 | (declare-function webkit--cookie-set-storage "webkit-module")
55 | (declare-function webkit--proxy-set-uri "webkit-module")
56 | (declare-function webkit--proxy-set-default "webkit-module")
57 | (declare-function webkit--execute-js "webkit-module")
58 | (declare-function webkit--add-user-style "webkit-module")
59 | (declare-function webkit--remove-all-user-styles "webkit-module")
60 | (declare-function webkit--add-user-script "webkit-module")
61 | (declare-function webkit--remove-all-user-scripts "webkit-module")
62 | (declare-function webkit--register-script-message "webkit-module")
63 | (declare-function webkit--unregister-script-message "webkit-module")
64 |
65 | (declare-function webkit-history-completing-read prompt "webkit-history")
66 |
67 | (declare-function bookmark-default-handler "bookmark" (bmk))
68 | (declare-function bookmark-prop-get "bookmark" (bookmark prop))
69 | (declare-function org-link-set-parameters "ol" (type &rest rest))
70 | (declare-function org-link-store-props "ol" (&rest plist))
71 |
72 | (defconst webkit--user-dir (locate-user-emacs-file "webkit/"))
73 | (make-directory webkit--user-dir t)
74 |
75 | (defun webkit--file-to-string (filename)
76 | (with-temp-buffer
77 | (insert-file-contents filename)
78 | (buffer-string)))
79 |
80 | (require 'browse-url)
81 | (require 'eww)
82 |
83 | (defvar webkit--id)
84 | (defvar webkit--buffers)
85 | (defvar webkit--scripts)
86 | (defvar webkit--styles)
87 | (defvar webkit--current-isearch-id)
88 |
89 | (defgroup webkit nil
90 | "The dynamic module webkit browser."
91 | :group 'convenience)
92 |
93 | (defcustom webkit-search-prefix "https://duckduckgo.com/html/?q="
94 | "Prefix URL to search engine."
95 | :type 'string
96 | :group 'webkit)
97 |
98 | (defcustom webkit-browse-url-force-new nil
99 | "Whether webkit should always open a new session instead of
100 | reusing a current one. Note this reverse that action of a prefix
101 | argument so C-u M-x `webkit' will open in the current session."
102 | :type 'boolean
103 | :group 'webkit)
104 |
105 | (defcustom webkit-own-window nil
106 | "Whether webkit should use its own window instead of
107 | attemptting to embed itself in its buffer. The curretly focused
108 | frame must be display-graphic-p and either x or pgtk when
109 | webkit-new is run in order for embedding to work."
110 | :type 'boolean
111 | :group 'webkit)
112 |
113 | (defcustom webkit-download-action-alist '((".*" . webkit-download-default))
114 | "Alist similar to `auto-mode-alist' that maps filename patterns
115 | to functions that will handle their download. Elements have the
116 | form of (REGEXP . FUNCTION) where function takes two arguments:
117 | the URL of the download and the corresponding NAME of the file."
118 | :type 'alist
119 | :group 'webkit)
120 |
121 | (defcustom webkit-configuration-directory
122 | (locate-user-emacs-file "url/" ".url/")
123 | "Directory used by the URL package for cookies, history, etc."
124 | :type 'directory
125 | :group 'url)
126 |
127 | (defcustom webkit-cookie-file (expand-file-name "cookies" webkit--user-dir)
128 | "File to store cookies of `webkit' sessions.
129 | Set to `nil' to disable saving cookies to a file."
130 | :type 'file
131 | :group 'webkit)
132 |
133 | (defvar webkit-mode-map
134 | (let ((map (make-sparse-keymap)))
135 | (define-key map "g" 'webkit)
136 | (define-key map "f" 'webkit-forward)
137 | (define-key map "b" 'webkit-back)
138 | (define-key map "r" 'webkit-reload)
139 | (define-key map "i" 'webkit-insert-mode)
140 | (define-key map "+" 'webkit-zoom-in)
141 | (define-key map "-" 'webkit-zoom-out)
142 | (define-key map (kbd "C-y") 'webkit-copy-selection)
143 | (define-key map (kbd "C-l") 'webkit-copy-url)
144 |
145 | (define-key map (kbd "C-s C-s") 'webkit-isearch)
146 | (define-key map (kbd "C-s s") 'webkit-search-next)
147 | (define-key map (kbd "C-s r") 'webkit-search-previous)
148 | (define-key map (kbd "C-s c") 'webkit-search-finish)
149 |
150 | ;;similar to image mode bindings
151 | (define-key map (kbd "SPC") 'webkit-scroll-up)
152 | (define-key map (kbd "S-SPC") 'webkit-scroll-down)
153 | (define-key map (kbd "DEL") 'webkit-scroll-down)
154 |
155 | (define-key map [remap scroll-up] 'webkit-scroll-up-line)
156 | (define-key map [remap scroll-up-command] 'webkit-scroll-up)
157 |
158 | (define-key map [remap scroll-down] 'webkit-scroll-down-line)
159 | (define-key map [remap scroll-down-command] 'webkit-scroll-down)
160 |
161 | (define-key map [remap forward-char] 'webkit-scroll-forward)
162 | (define-key map [remap backward-char] 'webkit-scroll-backward)
163 | (define-key map [remap right-char] 'webkit-scroll-forward)
164 | (define-key map [remap left-char] 'webkit-scroll-backward)
165 | (define-key map [remap previous-line] 'webkit-scroll-down-line)
166 | (define-key map [remap next-line] 'webkit-scroll-up-line)
167 |
168 | (define-key map [remap beginning-of-buffer] 'webkit-scroll-top)
169 | (define-key map [remap end-of-buffer] 'webkit-scroll-bottom)
170 | map)
171 | "Keymap for `webkit-mode'.")
172 |
173 | (defun webkit-zoom-in (&optional webkit-id)
174 | "Increase webkit view zoom factor."
175 | (interactive)
176 | (webkit--set-zoom (or webkit-id webkit--id)
177 | (+ (webkit--get-zoom (or webkit-id webkit--id)) 0.1)))
178 |
179 | (defun webkit-zoom-out (&optional webkit-id)
180 | "Decrease webkit view zoom factor."
181 | (interactive)
182 | (webkit--set-zoom (or webkit-id webkit--id)
183 | (+ (webkit--get-zoom (or webkit-id webkit--id)) -0.1)))
184 |
185 | (defun webkit-scroll-by-pixels (arg &optional webkit-id)
186 | "Scroll webkit up by ARG pixels.
187 |
188 | Stops if bottom of page is reached.
189 | Interactively, ARG is the prefix numeric argument.
190 | Negative ARG scrolls down."
191 | (interactive "P")
192 | (webkit--execute-js (or webkit-id webkit--id)
193 | (format "window.scrollBy(0, %d);" arg)))
194 |
195 | (defun webkit-scroll-by-percent (arg &optional webkit-id)
196 | "Scroll webkit up by ARG percent.
197 |
198 | Stops if bottom of page is reached.
199 | Interactively, ARG is the prefix numeric argument.
200 | Negative ARG scrolls down."
201 | (interactive "P")
202 | (pcase-let ((`(,left ,top ,right ,bottom)
203 | (window-inside-pixel-edges (selected-window))))
204 | (webkit--execute-js (or webkit-id webkit--id)
205 | (format "window.scrollBy(0, %d);"
206 | (* arg (- bottom top))))))
207 |
208 | (defun webkit-scroll-up (&optional webkit-id)
209 | "Scroll webkit up by full window height."
210 | (interactive)
211 | (webkit-scroll-by-percent 1 webkit-id))
212 |
213 | (defun webkit-scroll-down (&optional webkit-id)
214 | "Scroll webkit down by full window height."
215 | (interactive)
216 | (webkit-scroll-by-percent -1 webkit-id))
217 |
218 | (defun webkit-scroll-up-line (&optional n webkit-id)
219 | "Scroll webkit up by N lines.
220 | The height of line is calculated with `window-font-height'.
221 | Stop if the bottom edge of the page is reached.
222 | If N is omitted or nil, scroll up by one line."
223 | (interactive "p")
224 | (webkit-scroll-by-pixels (* n (window-font-height)) webkit-id))
225 |
226 | (defun webkit-scroll-down-line (&optional n webkit-id)
227 | "Scroll webkit down by N lines.
228 | The height of line is calculated with `window-font-height'.
229 | Stop if the top edge of the page is reached.
230 | If N is omitted or nil, scroll down by one line."
231 | (interactive "p")
232 | (webkit-scroll-by-pixels (* (* -1 n) (window-font-height)) webkit-id))
233 |
234 | (defun webkit-scroll-forward (&optional n webkit-id)
235 | "Scroll webkit horizontally by N chars.
236 | The width of char is calculated with `window-font-width'.
237 | If N is omitted or nil, scroll forwards by one char."
238 | (interactive "p")
239 | (webkit--execute-js
240 | (or webkit-id webkit--id)
241 | (format "window.scrollBy(%d, 0);"
242 | (* n (window-font-width)))))
243 |
244 | (defun webkit-scroll-backward (&optional n webkit-id)
245 | "Scroll webkit back by N chars.
246 | The width of char is calculated with `window-font-width'.
247 | If N is omitted or nil, scroll backwards by one char."
248 | (interactive "p")
249 | (webkit--execute-js
250 | (or webkit-id webkit--id)
251 | (format "window.scrollBy(-%d, 0);"
252 | (* n (window-font-width)))))
253 |
254 | (defun webkit-scroll-top (&optional webkit-id)
255 | "Scroll webkit to the very top."
256 | (interactive)
257 | (webkit--execute-js
258 | (or webkit-id webkit--id)
259 | "window.scrollTo(pageXOffset, 0);"))
260 |
261 | (defun webkit-scroll-bottom (&optional webkit-id)
262 | "Scroll webkit to the very bottom."
263 | (interactive)
264 | (webkit--execute-js
265 | (or webkit-id webkit--id)
266 | "window.scrollTo(pageXOffset, window.document.body.scrollHeight);"))
267 |
268 | (defun webkit--copy-selection-callback (selection)
269 | (let ((print-escape-newlines t)
270 | (text (elt (json-parse-string selection) 0)))
271 | (kill-new text)
272 | (message "copied \"%s\"" text)))
273 |
274 | (defun webkit-copy-selection (&optional webkit-id)
275 | "Copy the webkit selection to the kill ring."
276 | (interactive)
277 | (webkit--execute-js
278 | (or webkit-id webkit--id)
279 | "[window.getSelection().toString()];" "webkit--copy-selection-callback"))
280 |
281 | (defun webkit-copy-url (&optional webkit-id)
282 | "Copy the webkit url to the kill ring."
283 | (interactive)
284 | (let ((uri (webkit--get-uri (or webkit-id webkit--id))))
285 | (message "Copied %s" uri)
286 | (kill-new uri)))
287 |
288 | (defun webkit-forward (&optional webkit-id)
289 | "Go forward in history."
290 | (interactive)
291 | (webkit--forward (or webkit-id webkit--id)))
292 |
293 | (defun webkit-back (&optional webkit-id)
294 | "Go back in history."
295 | (interactive)
296 | (webkit--back (or webkit-id webkit--id)))
297 |
298 | (defun webkit-reload (&optional webkit-id)
299 | "Reload current URL."
300 | (interactive)
301 | (webkit--reload (or webkit-id webkit--id)))
302 |
303 | (defun webkit--search-cleanup ()
304 | (remove-hook 'post-command-hook #'webkit--search-update)
305 | (remove-hook 'minibuffer-exit-hook #'webkit--search-cleanup)
306 | (setq webkit--current-isearch-id nil))
307 |
308 | (defun webkit--search-update ()
309 | (let* ((str (minibuffer-contents))
310 | (case-fold-search nil)
311 | (case (string-match-p "[[:upper:]]" str)))
312 | (webkit--search webkit--current-isearch-id str case)))
313 |
314 | (defun webkit-isearch ()
315 | "Interactive incremental search current webkit buffer.
316 | Seach becomes case sensitive if query has any uppercase characters."
317 | (interactive)
318 | (setq webkit--current-isearch-id webkit--id)
319 | (minibuffer-with-setup-hook
320 | (lambda ()
321 | (add-hook 'post-command-hook #'webkit--search-update)
322 | (add-hook 'minibuffer-exit-hook #'webkit--search-cleanup))
323 | (read-string "Find: ")))
324 |
325 | (defun webkit-search (query &optional case webkit-id)
326 | "Search in webkit for QUERY.
327 | Seach is case sensitive if CASE is not nil."
328 | (interactive (list (read-string "Query: ") nil))
329 | (webkit--search (or webkit-id webkit--id) query case))
330 |
331 | (defun webkit-search-finish (&optional webkit-id)
332 | "Stop highlighting search results in webkit."
333 | (interactive)
334 | (webkit--search-finish (or webkit-id webkit--id)))
335 |
336 | (defun webkit-search-next (&optional webkit-id)
337 | "Go to next search result in webkit."
338 | (interactive)
339 | (webkit--search-next (or webkit-id webkit--id)))
340 |
341 | (defun webkit-search-previous (&optional webkit-id)
342 | "Go to previous search result in webkit."
343 | (interactive)
344 | (webkit--search-previous (or webkit-id webkit--id)))
345 |
346 | (defun webkit-start-web-inspector (&optional webkit-id)
347 | "Start webkit's webk inspector."
348 | (interactive)
349 | (webkit--start-web-inspector (or webkit-id webkit--id)))
350 |
351 | (defun webkit-set-styles (styles webkit-id)
352 | "Add all the strings of css scripts in the list STYLES to
353 | WEBKIT-ID while removing any previous styles."
354 | (webkit--remove-all-user-styles webkit-id)
355 | (dolist (style styles)
356 | (webkit--add-user-style webkit-id style)))
357 |
358 | (defun webkit-set-scripts (scripts webkit-id)
359 | "Add all the strings of js scripts in the list SCRIPTS to
360 | WEBKIT-ID while removing any previous scripts."
361 | (webkit--remove-all-user-scripts webkit-id)
362 | (dolist (script scripts)
363 | (webkit--add-user-script webkit-id script)))
364 |
365 | (defun webkit-add-style (style &optional webkit-id)
366 | "Add user css STYLE to webkit view."
367 | (push style webkit--styles)
368 | (webkit-set-styles webkit--styles (or webkit-id webkit--id)))
369 |
370 | (defun webkit-add-script (script &optional webkit-id)
371 | "Add user js SCRIPT to webkit view."
372 | (push script webkit--scripts)
373 | (webkit-set-scripts webkit--scripts (or webkit-id webkit--id)))
374 |
375 | (defun webkit-remove-style (style &optional webkit-id)
376 | "Remove user css STYLE frome webkit view."
377 | (setq webkit--styles (delq style webkit--styles))
378 | (webkit-set-styles webkit--styles (or webkit-id webkit--id)))
379 |
380 | (defun webkit-remove-script (script &optional webkit-id)
381 | "Remove user js SCRIPT frome webkit view."
382 | (setq webkit--scripts (delq script webkit--scripts))
383 | (webkit-set-scripts webkit--scripts (or webkit-id webkit--id)))
384 |
385 | (defun webkit-enable-javascript (&optional enable webkit-id)
386 | "Enable external javascript execution if ENABLE is not nil and
387 | disable it otherwise."
388 | (interactive "P")
389 | (webkit--enable-javascript (or webkit-id webkit--id) enable))
390 |
391 | (defun webkit-set-cookie-file (file &optional webkit-id)
392 | "Set persistent cookie storage location to FILE."
393 | (interactive (list (read-file-name "Cookie file: " webkit--user-dir)))
394 | (webkit--cookie-set-storage (or webkit-id webkit--id)
395 | (expand-file-name file)))
396 |
397 | (defun webkit-set-proxy (uri &optional webkit-id)
398 | "Set proxy to URI or reset to default if URI is the empty string.
399 | See webkit documentation for supported proxy uri types."
400 | (interactive (list (read-string "Proxy URI (blank to reset): ")))
401 | (if (string= uri "")
402 | (webkit--proxy-set-default webkit--id)
403 | (webkit--proxy-set-uri (or webkit-id webkit--id) uri)))
404 |
405 | (defun webkit-insert-mode (&optional webkit-id)
406 | (interactive)
407 | (message "Entering webkit insert mode, press C-g to exit")
408 | (webkit--focus (or webkit-id webkit--id)))
409 |
410 | (defun webkit--callback-unfocus (val)
411 | (ignore val)
412 | (message "C-g pressed in webkit... exiting insert mode")
413 | (webkit--unfocus webkit--id))
414 |
415 | (defun webkit--load-finished (msg)
416 | (ignore msg)
417 | (run-hooks 'webkit-load-finished-hook))
418 |
419 | (defun webkit--callback-title (title)
420 | (run-hook-with-args 'webkit-title-changed-functions title))
421 |
422 | (defun webkit--callback-uri (uri)
423 | (run-hook-with-args 'webkit-uri-changed-functions uri))
424 |
425 | (defun webkit--callback-progress (progress)
426 | (run-hook-with-args 'webkit-progress-changed-functions
427 | (string-to-number progress)))
428 |
429 | (defvar-local webkit--progress-formatted nil
430 | "Formatted string for display of the load progress.
431 | Padded with spaces if necessary.")
432 |
433 | (defun webkit--display-progress (progress)
434 | "Set `webkit--progress-formatted' to the formatted string of PROGRESS.
435 |
436 | `webkit--progress-formatted' should be then displayed in the
437 | modeline as a part of `mode-name'"
438 | (setq webkit--progress-formatted
439 | (if (equal progress 100.0)
440 | ""
441 | (format "loading:%.0f%% " progress)))
442 | (force-mode-line-update))
443 |
444 | (defun webkit--callback-new-view (uri)
445 | (webkit-new uri))
446 |
447 | ;; https://lists.gnu.org/archive/html/bug-gnu-emacs/2020-11/msg02193.html
448 | (defun webkit--make-unique-file-name (file directory)
449 | (cond
450 | ((zerop (length file))
451 | (setq file "!"))
452 | ((string-match "\\`[.]" file)
453 | (setq file (concat "!" file))))
454 | (let ((count 1)
455 | (stem file)
456 | (suffix ""))
457 | (when (string-match "\\`\\(.*\\)\\([.][^.]+\\)" file)
458 | (setq stem (match-string 1 file)
459 | suffix (match-string 2 file)))
460 | (while (file-exists-p (expand-file-name file directory))
461 | (setq file (format "%s(%d)%s" stem count suffix))
462 | (setq count (1+ count)))
463 | (expand-file-name file directory)))
464 |
465 | (defun webkit-download-save-buffer-safe (dir name)
466 | (let ((file (webkit--make-unique-file-name name dir)))
467 | (goto-char (point-min))
468 | (re-search-forward "\r?\n\r?\n")
469 | (let ((coding-system-for-write 'no-conversion))
470 | (write-region (point) (point-max) file))
471 | (message "Saved %s" file)))
472 |
473 | (defun webkit-download-open-as-buffer (name)
474 | (rename-buffer name t)
475 | (goto-char (point-min))
476 | (re-search-forward "\r?\n\r?\n")
477 | (delete-region (point-min) (point))
478 | (let ((mode (assoc-default name auto-mode-alist 'string-match)))
479 | (if mode
480 | (funcall mode)
481 | (fundamental-mode)))
482 | (switch-to-buffer (current-buffer)))
483 |
484 | (defun webkit-download-default-callback (status url name)
485 | (if (plist-get status :error)
486 | (error "Unable to download %S" url)
487 | (if (y-or-n-p "Save to disk? Otherwise download will open in temp buffer")
488 | (let ((file (read-file-name "Save as " (erc--download-directory)
489 | nil nil name)))
490 | (webkit-download-save-buffer-safe (file-name-directory file)
491 | (file-name-nondirectory file)))
492 | (webkit-download-open-as-buffer name))))
493 |
494 | (defun webkit-download-open-callback (status url name)
495 | (if (plist-get status :error)
496 | (error "Unable to download %S" url)
497 | (webkit-download-open-as-buffer name)))
498 |
499 | (defun webkit-download-save-callback (status url name)
500 | (if (plist-get status :error)
501 | (error "Unable to download %S" url)
502 | (webkit-download-save-buffer-safe (erc--download-directory) name)))
503 |
504 | (defun webkit-download-default (url name)
505 | "Downloads the resource at URL.
506 |
507 | Prompts user on whether the download should be opened in a
508 | temporary buffer or saved. If it is to be saved, prompts user for
509 | save path starting from user's download directory with suggested
510 | filename NAME."
511 | (url-retrieve url #'webkit-download-default-callback (list url name)))
512 |
513 | (defun webkit-download-open (url name)
514 | "Downloads the resource at URL.
515 |
516 | Opens download in tempoary buffer named NAME."
517 | (url-retrieve url #'webkit-download-open-callback (list url name)))
518 |
519 | (defun webkit-download-save (url name)
520 | "Downloads the resource at URL.
521 |
522 | Saves download in user's Downloads directory with filename NAME."
523 | (url-retrieve url #'webkit-download-save-callback (list url name)))
524 |
525 | (defun webkit--callback-download-request (url)
526 | (let* ((obj (url-generic-parse-url url))
527 | (path (directory-file-name (car (url-path-and-query obj))))
528 | (name (eww-decode-url-file-name (file-name-nondirectory path))))
529 | (funcall (assoc-default name webkit-download-action-alist 'string-match)
530 | url name)))
531 |
532 | (defun webkit--bookmark-handler (bmk-record)
533 | (let* ((file (bookmark-prop-get bmk-record 'filename))
534 | (buf (webkit-browse-url file t)))
535 | (bookmark-default-handler `("" (buffer . ,buf)))))
536 |
537 | (defun webkit--bookmark-make-record ()
538 | `(,(buffer-name)
539 | (filename . ,(webkit--get-uri webkit--id))
540 | (handler . webkit--bookmark-handler)))
541 |
542 | (org-link-set-parameters "webkit" :store #'webkit-org-store-link)
543 |
544 | (defun webkit-org-store-link ()
545 | "Store a link to the url of an EWW buffer."
546 | (when (eq major-mode 'webkit-mode)
547 | (org-link-store-props
548 | :type "http"
549 | :link (webkit--get-uri webkit--id)
550 | :description (buffer-name))))
551 |
552 | (defun webkit-rename-buffer (title)
553 | (if (string= "" title)
554 | (let ((uri (webkit--get-uri webkit--id)))
555 | (if (string= "" uri)
556 | (rename-buffer "*webkit*" t)
557 | (rename-buffer uri t)))
558 | (rename-buffer title t)))
559 |
560 | (defun webkit--filter (proc string)
561 | (when (buffer-live-p (process-buffer proc))
562 | (with-current-buffer (process-buffer proc)
563 | (goto-char (point-max))
564 | (insert string)
565 | (goto-char 1)
566 | (while (re-search-forward "\\([^\x00]*\\)\x00\\([^\x00]*\\)\x00" nil t)
567 | (let ((id (match-string 1))
568 | (msg (match-string 2)))
569 | (delete-region 1 (match-end 0))
570 | ;(message "id: %s; message: %s" id msg)
571 | (funcall (intern id) msg))))))
572 |
573 | (defun webkit--move-to-x-or-pgtk-frame (frame)
574 | (let* ((ws (window-system frame))
575 | (err-msg "Cannot move webkit view to frame with window-system %S")
576 | (win-id (string-to-number (frame-parameter frame 'window-id)))
577 | (win-id (cond ((eq ws 'pgtk) win-id)
578 | ((eq ws 'x) (webkit--xid-to-pointer win-id))
579 | (t (error err-msg ws)))))
580 | (webkit--move-to-frame webkit--id win-id)))
581 |
582 | (defun webkit--adjust-size (frame)
583 | (ignore frame)
584 | (dolist (buffer webkit--buffers)
585 | (when (buffer-live-p buffer)
586 | (with-current-buffer buffer
587 | (let* ((windows (get-buffer-window-list buffer 'nomini t)))
588 | (if (not windows)
589 | (webkit--hide webkit--id)
590 | (let* ((show-window (if (memq (selected-window) windows)
591 | (selected-window)
592 | (car windows)))
593 | (hide-windows (remq show-window windows))
594 | (show-frame (window-frame show-window)))
595 | (webkit--move-to-x-or-pgtk-frame show-frame)
596 | (pcase-let ((`(,left ,top ,right ,bottom)
597 | (window-inside-pixel-edges show-window)))
598 | (webkit--show webkit--id)
599 | (webkit--resize webkit--id left top
600 | (- right left) (- bottom top)))
601 | (dolist (window hide-windows)
602 | (switch-to-prev-buffer window)))))))))
603 |
604 | (defun webkit--delete-frame (frame)
605 | (let ((new-frame (car (seq-filter
606 | (lambda (elt)
607 | (not (or (eq elt frame)
608 | (frame-parameter elt 'parent-frame)
609 | (not (display-graphic-p elt)))))
610 | (frame-list)))))
611 | (seq-map (lambda (buffer) (with-current-buffer buffer
612 | (webkit--move-to-x-or-pgtk-frame new-frame)))
613 | webkit--buffers)))
614 |
615 | (defun webkit--close (msg)
616 | (ignore msg)
617 | (set-process-query-on-exit-flag (get-buffer-process (current-buffer)) nil)
618 | (kill-this-buffer))
619 |
620 | (defun webkit--kill-buffer ()
621 | (webkit--hide webkit--id)
622 | (webkit--destroy webkit--id)
623 | (setq webkit--buffers (delq (current-buffer) webkit--buffers)))
624 |
625 | (defun webkit-new (&optional url buffer-name noquery)
626 | "Create a new webkit with URL
627 |
628 | If called with an argument BUFFER-NAME, the name of the new buffer will
629 | be set to BUFFER-NAME, otherwise it will be `webkit'.
630 | Returns the newly created webkit buffer"
631 | (let ((buffer (generate-new-buffer (or buffer-name "*webkit*"))))
632 | (with-current-buffer buffer
633 | (webkit-mode)
634 | (setq webkit--id (webkit--new
635 | (make-pipe-process :name "webkit"
636 | :buffer buffer
637 | :filter 'webkit--filter
638 | :noquery noquery)
639 | webkit-own-window))
640 | (push buffer webkit--buffers)
641 | (webkit--register-script-message webkit--id "webkit--callback-unfocus")
642 | (when webkit-cookie-file (webkit-set-cookie-file webkit-cookie-file))
643 | (run-hooks 'webkit-new-hook)
644 | (when url (webkit--load-uri webkit--id url))
645 | (switch-to-buffer buffer))))
646 |
647 | ;;;###autoload
648 | (defun webkit-browse-url (url &optional new-session)
649 | "Goto URL with webkit using browse-url.
650 |
651 | NEW-SESSION specifies whether to create a new webkit session or use the
652 | current session."
653 | (interactive (browse-url-interactive-arg "URL: "))
654 | (if webkit-browse-url-force-new (setq new-session (not new-session)))
655 | (if (or new-session (not webkit--buffers))
656 | (webkit-new url)
657 | (let* ((id (or webkit--id (with-current-buffer (car webkit--buffers)
658 | webkit--id)))
659 | (buffer (seq-find (lambda (buffer)
660 | (with-current-buffer buffer
661 | (when (eq id webkit--id) buffer)))
662 | webkit--buffers)))
663 | (webkit--load-uri id url)
664 | (switch-to-buffer buffer))))
665 |
666 | ;;;###autoload
667 | (defun webkit (url &optional arg)
668 | "Fetch URL and render the page.
669 | If the input doesn't look like an URL or a domain name, the
670 | word(s) will be searched for via `webkit-search-prefix'.
671 |
672 | If called with a prefix ARG, create a new webkit buffer instead of reusing
673 | the default webkit buffer."
674 | (interactive
675 | (let ((prompt "URL or keywords: "))
676 | (list
677 | (if (require 'webkit-history nil t)
678 | (webkit-history-completing-read prompt)
679 | (read-string prompt))
680 | (prefix-numeric-value current-prefix-arg))))
681 | (let ((eww-search-prefix webkit-search-prefix))
682 | (webkit-browse-url (eww--dwim-expand-url url) (eq arg 4))))
683 |
684 | (define-derived-mode webkit-mode special-mode
685 | '("" webkit--progress-formatted "WebKit")
686 | "webkit view mode."
687 | (setq buffer-read-only nil)
688 | (setq-local cursor-type nil)
689 |
690 | (setq-local bookmark-make-record-function #'webkit--bookmark-make-record)
691 |
692 | (add-hook 'kill-buffer-hook #'webkit--kill-buffer nil t))
693 |
694 | (unless (require 'webkit-module nil t)
695 | (error "webkit needs `webkit-module' to be compiled!"))
696 |
697 | (when (version< emacs-version "28.0")
698 | (error "webkit requires an Emacs version > 28"))
699 |
700 | ;;(defun webkit-setup ()
701 | ;; "Setup various hooks necessary for webkit to work.
702 | ;;`webkit-own-window' must be set to desired value before this is called."
703 |
704 | (make-variable-buffer-local 'webkit--id)
705 | (make-variable-buffer-local 'webkit--scripts)
706 | (make-variable-buffer-local 'webkit--styles)
707 | (setq webkit--buffers nil)
708 |
709 | (unless webkit-own-window
710 | (add-hook 'window-size-change-functions #'webkit--adjust-size)
711 | (add-hook 'delete-frame-functions #'webkit--delete-frame))
712 |
713 | (add-hook 'webkit-title-changed-functions #'webkit-rename-buffer)
714 | (add-hook 'webkit-progress-changed-functions #'webkit--display-progress)
715 |
716 | (provide 'webkit)
717 | ;;; webkit.el ends here
718 |
--------------------------------------------------------------------------------