Compare commits
102 Commits
Author | SHA1 | Date | |
---|---|---|---|
2af3abcf8c | |||
9dda522a26 | |||
9b1b8ca1ed | |||
c2a08ece47 | |||
d1b2259d81 | |||
cb9ec6141c | |||
24b95be6dc | |||
9136ab2565 | |||
1438d3037a | |||
fdf688cba2 | |||
82584d6a59 | |||
5e3cab2a63 | |||
2a5f09ce7c | |||
7a54b7bd99 | |||
b922d15f98 | |||
c4a4a73302 | |||
708193f82a | |||
459784029b | |||
c3d627f922 | |||
9c2c34743f | |||
20a0c15caa | |||
c3ac0c0333 | |||
51287bc420 | |||
56ed98703d | |||
bdfddd3a3a | |||
b138c2c83a | |||
db9d3e66f2 | |||
e03b6cc447 | |||
258aa8dd3c | |||
6fd0a9e40f | |||
3294947ad4 | |||
d7781d308d | |||
db4f980af9 | |||
b2d518e184 | |||
6f6a327aa6 | |||
d2d74b3ef9 | |||
418cb6f150 | |||
37d00ae8ee | |||
0f8885995a | |||
e85c727798 | |||
ad840fe91d | |||
d03dbede60 | |||
7f1074b318 | |||
42d09714a8 | |||
143b6c76d8 | |||
6f3f56c604 | |||
61a1d4f0f3 | |||
c716e6b5c5 | |||
83e80a7dda | |||
4902ed2398 | |||
cbbb5fe83c | |||
ac0cd38fea | |||
4ce1a4265e | |||
d19fd76a0c | |||
805fd6d45a | |||
6f98d00f28 | |||
db4fe9a0cf | |||
3ac35677a5 | |||
b2793329c8 | |||
4f3a90daaf | |||
e124f5c561 | |||
2c5129ccaa | |||
d069578e61 | |||
e6c1913f0d | |||
6e7966b3b3 | |||
a7e0a98b1d | |||
bf23c75dfd | |||
4155862a6a | |||
d09bb0d51d | |||
b1adaef7e3 | |||
68b6d1cdb6 | |||
90521e0a18 | |||
f44bd92891 | |||
80e7e576c2 | |||
6c6c84ec3b | |||
20a5699818 | |||
84d35b197d | |||
286dc05d45 | |||
bb9ff083b3 | |||
de1e608d06 | |||
1c9fbe1122 | |||
a0f79388e9 | |||
126fdc32fd | |||
40da9afd4d | |||
10fca6af34 | |||
15451be030 | |||
80424d2854 | |||
f5a03f4d05 | |||
99ad21b850 | |||
8aa43e81fc | |||
c4581f3fd5 | |||
7d6c478c9c | |||
9cb8bd5818 | |||
0e3956fea1 | |||
c95ed11bc3 | |||
0c282697bc | |||
70849e74d6 | |||
136ad04678 | |||
a1a22bb282 | |||
3aa9e5fa39 | |||
0556d10bb8 | |||
875b6fec05 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
rosenrot
|
||||
# don't save the binary file, as it doesn't play nicely with https://difftastic.wilfred.me.uk
|
||||
webkit/
|
||||
|
|
29
README.md
29
README.md
|
@ -1,6 +1,6 @@
|
|||
# Rosenrot
|
||||
|
||||
Rosenrot is a small browser forked from an earlier version of [rose](https://github.com/mini-rose/rose). It has some additional quality of life improvements tailored to my (@NunoSempere) tastes and setup, and detailed installation instructions for Debian 12.
|
||||
Rosenrot is a small browser forked from an earlier version of [rose](https://github.com/mini-rose/rose). It has some additional quality of life improvements tailored to my (@NunoSempere) tastes and setup, and detailed installation instructions for Debian 12. Because of the ease of hackability, the minimalism, the unobstrusiveness, it gives me a more comfortable way of navigating the web than normal browsers.
|
||||
|
||||

|
||||
|
||||
|
@ -10,12 +10,12 @@ Rosenrot is a small browser forked from an earlier version of [rose](https://git
|
|||
|
||||
### Installation and usage
|
||||
|
||||
You can see detailed instructions [here](./user-scripts/debian-12/install-with-dependencies.sh), for Debian 12 in particular—though they should generalize easily to other distributions.
|
||||
You can see detailed instructions [here](./user-scripts/debian-12/), for Debian 12 in particular—though they should generalize easily to other distributions.
|
||||
|
||||
The general steps are to install dependencies, and then
|
||||
|
||||
```
|
||||
make build
|
||||
make build # by default using webkitgtk6/gtk4; see also build3
|
||||
make install # or sudo make install
|
||||
rose
|
||||
```
|
||||
|
@ -44,6 +44,7 @@ You can also create a rose.desktop file so that it will show up in your desktop
|
|||
- Custom style: Override the css of predetermined websites
|
||||
- Max number of tabs (by default 8), configurable.
|
||||
- Stand in plugin: Mimick function definitions which do nothing for the above plugins so that they can be quickly removed
|
||||
- For now, both GTK3 and GTK4-based versions—eventually only the GTK4 version will remain
|
||||
|
||||
You can see some screenshots in the [images](./images) folder.
|
||||
|
||||
|
@ -66,14 +67,14 @@ Here are projects with their own rendering engines which could appeal to users o
|
|||
|
||||
- [lynx](https://lynx.invisible-island.net/) (links, elinks), [w3m](https://w3m.sourceforge.net/): command line browsers.
|
||||
- [dillo](https://github.com/dillo-browser/dillo/). Has its own rendering engine, and no javascript.
|
||||
- [Ladybird](https://github.com/SerenityOS/serenity/tree/master/Ladybird). SerenityOS. Uses its own html and javascript engine. Compiling it on a mainstream Linux distribution, and documenting instructions could be an interesting project, but the few times I've tried that I've failed.
|
||||
- [Ladybird](https://github.com/LadybirdBrowser/ladybird). Initially from the InitialSerenityOS, it later became its own project. Uses its own html and javascript engine. Compiling it on a mainstream linux distribution is now doable.
|
||||
- [servo](https://github.com/servo/servo). Firefox/Mozilla. An in-development browser engine written in Rust, meant to replace Gecko. Could be extremely cool once it is ready, but it has been many years in development.
|
||||
|
||||
### Relationship with [rose](https://github.com/mini-rose/rose)
|
||||
|
||||
- Rose is a small browser based on webkit2gtk. Previously, it described itself as aiming to be a "basement for creating your own browser using [the] gtk and webkit libraries". It has since diverged into a more featureful small browser with lua bindings, and rebased its history. You can see the original, minimal version [here](https://github.com/NunoSempere/rosenrot-browser/blob/a45d1c70f58586fed97df70650e5d066b73d0a0d/rose.c).
|
||||
- The current version offers compilation with both GTK3 and GTK4, and an up to date version of webkit.
|
||||
- Rosenrot is my (@NunoSempere's) fork from that earlier minimal rose. It has accumulated quality of life features and, honestly, cruft, that I like, like a "readability" plugin that simplifies annoying websites like [Matt Levine's Money Stuff newsletter](https://www.bloomberg.com/opinion/articles/2022-10-18/matt-levine-s-money-stuff-credit-suisse-was-a-reverse-meme-stock). It also incorporates ad-blocking.
|
||||
- Rosenrot is my (@NunoSempere's) fork from that earlier minimal rose, the GTK 3 version. It has accumulated quality of life features and, honestly, cruft, that I like, like a "readability" plugin that simplifies annoying websites like [Matt Levine's Money Stuff newsletter](https://www.bloomberg.com/opinion/articles/2022-10-18/matt-levine-s-money-stuff-credit-suisse-was-a-reverse-meme-stock). It also incorporates ad-blocking.
|
||||
- rose updated to allow compilation with an up-to-date version of webkit on both GTK3 and GTK4 earlier, though rosenrot now offers this as well.
|
||||
- Rosenrot is also a song by the German hardcore rock band [Rammstein](https://www.youtube.com/watch?v=af59U2BRRAU).
|
||||
|
||||
### Comparison with [surf](https://git.suckless.org/surf/file/surf.c.html)
|
||||
|
@ -85,9 +86,8 @@ Here are projects with their own rendering engines which could appeal to users o
|
|||
- Anecdotically, surf feels slower, though I haven't tested this rigorously.
|
||||
- surf has a larger community, with patches and modifications.
|
||||
- surf is more opinionated, but also less amateurish.
|
||||
- Like rosenrot until very recently, it [uses](https://git.suckless.org/surf/file/config.mk.html#l15) an obsolete & deprecated version of [webkit](https://blogs.gnome.org/mcatanzaro/2023/03/21/webkitgtk-api-for-gtk-4-is-now-stable/)
|
||||
- My recommendation would be to use rosenrot, and if you find some feature missing, either look how surf does it and import it to rose, or move to surf.
|
||||
- But then again, I've built rosenrot to cater to my own tastes, so I'd say that.
|
||||
- Like rosenrot until not so long ago, it [uses](https://git.suckless.org/surf/file/config.mk.html#l15) an obsolete & deprecated version of [webkit](https://blogs.gnome.org/mcatanzaro/2023/03/21/webkitgtk-api-for-gtk-4-is-now-stable/) (2.40, with GTK3)
|
||||
- My recommendation would be to use rosenrot, and if you find some feature missing, either look how surf does it and import it to rose, or move to surf. But then again, I've built rosenrot to cater to my own tastes, so I'd say that.
|
||||
|
||||
## Folk wisdom
|
||||
|
||||
|
@ -95,22 +95,23 @@ Of general interest:
|
|||
|
||||
- I just found out that you can inspect a GTK application with the GTK explorer if you set a certain command-line variable. Try this with `make inspect`.
|
||||
- Static variables keep their value between invocations.
|
||||
- By default the searchbar is pretty gigantic. I've made this so because I'm a bit myopic, but also work with my laptop in a laptop stand. Anyways, if you are a more normal person you can change this in the style.css.
|
||||
- The style.css usage isn't updated until installation. This is because by default rose uses the theme located in /usr/share/themes/rose/style.css, and that file isn't updated until make install.
|
||||
- By default the searchbar is pretty gigantic. I've made this so because I'm a bit myopic, but also work with my laptop in a laptop stand. Anyways, if you are a more normal person you can change this in the style/style-gtk4.css style/style-gtk3.css files.
|
||||
- The gtk style usage isn't updated until installation. This is because by default rose uses the theme located in /opt/rosenrot/style-gtk3/4.css, and that file isn't updated until make install.
|
||||
- The [min](https://git.nunosempere.com/open.source/rosenrot/src/branch/min) branch contains a minimalistic version of rosenrot, in one c file with 320 lines (256 without comments and extra newlines). It might be of interest to developers and those seeking to understand the code.
|
||||
|
||||
The "architecture" of the application looks as follows:
|
||||
|
||||

|
||||
|
||||
## webkit2gtk-4.0 vs webkit2gtk-4.1 vs webkit2gtk-6.0
|
||||
### webkit2gtk-4.0 vs webkit2gtk-4.1 vs webkit2gtk-6.0
|
||||
|
||||
See [this blog post](https://blogs.gnome.org/mcatanzaro/2023/03/21/webkitgtk-api-for-gtk-4-is-now-stable/) for details. webkit2gtk-4.0 is deprecated, webkit2gtk-4.1 is the current [stable](https://webkitgtk.org/reference/webkit2gtk/stable/index.html) release and uses GTK3. webkit2gtk-6.0 is the current [unstable](https://webkitgtk.org/reference/webkitgtk/unstable/index.html) release, and uses GTK4.
|
||||
|
||||
Migration instructions for migration to webkit2gtk-6 and GTK4 can be seen [here](https://github.com/WebKit/WebKit/blob/ed1422596dce5ff012e64a38faf402ac1674fc7e/Source/WebKit/gtk/migrating-to-webkitgtk-6.0.md) and [here](https://docs.gtk.org/gtk4/migrating-3to4.html).
|
||||
|
||||
Rosenrot is currently on the stable webkit2gtk-4.1 release using GTK3, and has removed deprecated webkit apis. It has plans to eventually migrate to webkit2gtk-6.0 eventually but not soon, because the GTK4 rewrite seems onerous.
|
||||
Rosenrot currently supports both the stable webkit2gtk-4.1/GTK3 release, and a newer release using webkit2gtk-6.0/GTK4. Eventually the later will become the only version. Readers might want to look through the history to see a bit about the updating process; commits related to the transition are tagged with the "GTK4: " label.
|
||||
|
||||
## Ubuntu 20.04
|
||||
### Ubuntu 20.04
|
||||
|
||||
A previous version of this repository was based on Ubuntu 20.04. You can still see documentation for that distribution [here](https://git.nunosempere.com/open.source/rosenrot/src/commit/8a1e0be30df52d5a21109297fd5bbc20efec1b3b), particularly a video installing rosenrot in a fresh Ubuntu 20.04 virtual machine [here](https://video.nunosempere.com/w/t3oAvJLPHTSAMViQ6zbwTV). However, that uses the webkit2gtk-4.0 library. Instead, I recommend adapting the Debian 12 instructions.
|
||||
|
||||
|
|
35
TODO.md
35
TODO.md
|
@ -1,21 +1,25 @@
|
|||
# To do
|
||||
|
||||
- [ ] Settle on a C standard (C11?), and use safer string handling functions provided by it.
|
||||
- [ ] Look into improving speed and performance:
|
||||
- [ ] Creating objects only once, e.g., for js strings that I execte
|
||||
- [ ] Look into using global controllers, rather than one for each webview
|
||||
- [ ] etc.
|
||||
- [ ] Move to a later C standard (C11?) and use safer string handling functions provided by it.
|
||||
- The thing is, I kinda feel attached to C89-C99
|
||||
- [ ] Consider
|
||||
- See make lint for purported insecurities
|
||||
- [ ] Document creating new applications, e.g., as in [Asana for Linux](https://git.nunosempere.com/NunoSempere/asana-for-linux)
|
||||
- [ ] This time, use something other than Whatsapp as an example syslink.
|
||||
- [ ] Fix bug about distorted audio. Maybe related to [this pipewire issue](<https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/1547>)?
|
||||
- See whether it even exists at all
|
||||
- [ ] Upgrade to GTK-4 / Webkitgtk 6.0? Will take a fair amount of time, since GTK4 redesigns the application model somewhat.
|
||||
- Instructions for webkit-6.0 [here](https://github.com/WebKit/WebKit/blob/ed1422596dce5ff012e64a38faf402ac1674fc7e/Source/WebKit/gtk/migrating-to-webkitgtk-6.0.md)
|
||||
- Instructions for GTK-4 [here](https://docs.gtk.org/gtk4/migrating-3to4.html)
|
||||
- [ ] Prepare for GTK-3 to GTK-4 transition
|
||||
- [ ] Understand wtf is going on with signals and events: <https://docs.gtk.org/gtk4/migrating-3to4.html#stop-using-gtkwidget-event-signals>. <https://github.com/mini-rose/rose-browser/blob/288bf060d095c4895946669ae50d14193168b69c/src/window.c#L42>
|
||||
- [ ] Remove webkit2gtk-4.1 and download webkit2gtk-6.0
|
||||
- [ ] Attempt to compile
|
||||
- [ ] Finish cleaning up GTK4 version
|
||||
- [ ] Think about best way of having GTK4 version alongside
|
||||
- [ ] Shortcut to resize window
|
||||
|
||||
# Previously done
|
||||
|
||||
- [x] Check that this compiles with the c99 standard
|
||||
- [x] Add minimalist version of rosenrot to its own branch
|
||||
- [x] Fix PageUp/PageDown shortcuts.
|
||||
- ~~[ ] Set [`webkit_web_context_set_sandbox_enabled`](<https://webkitgtk.org/reference/webkit2gtk/2.36.8/WebKitWebContext.html#webkit-web-context-set-sandbox-enabled>), as recommended [here](<https://blogs.gnome.org/mcatanzaro/2022/11/04/stop-using-qtwebkit/>)~~. Irrelevant with upgrade to libsoup3.
|
||||
- [x] Update to webkit2gtk-4.1
|
||||
|
@ -54,4 +58,17 @@
|
|||
- Useful for opening links in new tab when clicking on them and selecting that option
|
||||
- And for actually opening links with the href new_tab option.
|
||||
- Links: [1](<https://docs.gtk.org/gobject/func.signal_connect.html>), [2](<https://webkitgtk.org/reference/webkit2gtk/2.37.90/signal.AutomationSession.create-web-view.html>), [3](<https://webkitgtk.org/reference/webkit2gtk/2.26.0/WebKitWebView.html#WebKitWebView-create>), [4](<https://stackoverflow.com/questions/40180757/webkit2gtk-get-new-window-link>)
|
||||
|
||||
- [x] Upgrade to GTK-4 / Webkitgtk 6.0? Will take a fair amount of time, since GTK4 redesigns the application model somewhat.
|
||||
- Instructions for webkit-6.0 [here](https://github.com/WebKit/WebKit/blob/ed1422596dce5ff012e64a38faf402ac1674fc7e/Source/WebKit/gtk/migrating-to-webkitgtk-6.0.md)
|
||||
- Instructions for GTK-4 [here](https://docs.gtk.org/gtk4/migrating-3to4.html)
|
||||
- [x] Prepare for GTK-3 to GTK-4 transition
|
||||
- [x] Understand wtf is going on with signals and events: <https://docs.gtk.org/gtk4/migrating-3to4.html#stop-using-gtkwidget-event-signals>. <https://github.com/mini-rose/rose-browser/blob/288bf060d095c4895946669ae50d14193168b69c/src/window.c#L42>
|
||||
- [x] Stop using direct access to GdkEvent structs
|
||||
- [ ] ~~Remove webkit2gtk-4.1 and download webkit2gtk-6.0~~ => Actually just use both
|
||||
- [x] Attempt to compile
|
||||
- Notes for others:
|
||||
- Searching github
|
||||
- Creating a minimal version, e.g., having only one signal going on
|
||||
- Looking at how https://github.com/mini-rose/rose-browser/ does things
|
||||
- Printf statements
|
||||
- GTK ressources: https://docs.gtk.org/gtk4, https://docs.gtk.org/gobject/, https://docs.gtk.org/gdk4/
|
||||
|
|
152
config.h
152
config.h
|
@ -1,93 +1,106 @@
|
|||
#include <stdbool.h>
|
||||
#include <gdk/gdk.h>
|
||||
// Previously: #include <gdk/gdkkeysyms.h>
|
||||
// But GTK3 discourages including the individual headers
|
||||
// In GTK4, the location also changes to <gdk/gdkenums.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
// Key user config
|
||||
#define WIDTH 1920 // 960 for half-width, 1920 for full width
|
||||
#define HEIGHT 1080
|
||||
#define BAR_SIZE 1000
|
||||
/* Key user config */
|
||||
#define HEIGHT 1504
|
||||
#define FULL_WIDTH 2256
|
||||
#define WIDTH FULL_WIDTH
|
||||
#define BAR_WIDTH FULL_WIDTH/2
|
||||
|
||||
// More user config
|
||||
#define ZOOM 1.6 /* Starting zoom level.*/
|
||||
#define ZOOM_VAL .1 /* Zooming value in zoomin/zoomout functions */
|
||||
#define BG_COLOR "#FEFEFE" /* "FEFEFE", "#1E1E2E" */
|
||||
#define DEBUG false
|
||||
#define MAX_NUM_TABS 8 // set to 0 or false if you want unlimited tabs, or look at the relevant rose.c code.
|
||||
/* More user config */
|
||||
#define ZOOM_START_LEVEL 1.5
|
||||
#define ZOOM_STEPSIZE .1
|
||||
#define MAX_NUM_TABS 8 // 0 or false for unlimited tabs
|
||||
#define SEARCH "https://search.brave.com/search?q=%s"
|
||||
#define HOME "https://search.brave.com/search"
|
||||
// #define SEARCH "https://search.nunosempere.com/search?q=%s"
|
||||
// #define SEARCH "https://lite.duckduckgo.com/html/?q=%s"
|
||||
#define HOME "https://search.brave.com/search?q=%s"
|
||||
// ^ Could also be a website ("https://search.nunosempere.com"), or a file ("file:///opt/rose/homepage.png")
|
||||
// #define HOME "https://search.nunosempere.com/"
|
||||
// #define HOME "file:///opt/rosenrot/rose.png"
|
||||
// #define HOME "file:///opt/rosenrot/flower-imgs/rose-homepage.png"
|
||||
|
||||
// Plugins
|
||||
/* Plugins */
|
||||
#define LIBRE_REDIRECT_ENABLED true
|
||||
#define READABILITY_ENABLED true
|
||||
#define CUSTOM_STYLE_ENABLED true
|
||||
#define CUSTOM_USER_AGENT false
|
||||
/*
|
||||
To disable plugins:
|
||||
1. set their corresponding variable to false
|
||||
2. you could also look into this file at commit afe93518a for an approach using stand-in code.
|
||||
3. recompile
|
||||
2. recompile
|
||||
|
||||
To remove plugins completely;
|
||||
1. Remove the corresponding code in the rosenrot.c by looking for the variables above.
|
||||
1. Remove the corresponding code in rosenrot.c by looking for the variables above, as well as custom_style_enabled
|
||||
2. Remove PLUGIN and $(PLUGIN) from the makefile
|
||||
3. Recompile
|
||||
|
||||
You could also look into commit afe93518a for an approach using stand-in code.
|
||||
*/
|
||||
|
||||
// Webkit settings
|
||||
// See: https://webkitgtk.org/reference/webkit2gtk/stable/class.Settings.html
|
||||
/* Webkit */
|
||||
// https://webkitgtk.org/reference/webkit2gtk/stable/class.Settings.html
|
||||
#define WEBKIT_DEFAULT_SETTINGS \
|
||||
"enable-back-forward-navigation-gestures", true, "enable-developer-extras", true, \
|
||||
"enable-back-forward-navigation-gestures", true, \
|
||||
"enable-developer-extras", true, \
|
||||
"enable-smooth-scrolling", false, \
|
||||
"default-charset", "utf-8"
|
||||
/* CACHE */
|
||||
#define DATA_DIR "/home/nuno/.cache/rosenrot"
|
||||
#define DATA_MANAGER_OPTS "base-cache-directory", DATA_DIR, "base-data-directory", DATA_DIR
|
||||
#define NETWORK_SESSION_OPTS DATA_DIR, DATA_DIR
|
||||
|
||||
// GTK
|
||||
/* GTK */
|
||||
// https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/gdkkeysyms.h
|
||||
// https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/gdkenums.h
|
||||
#define GTK_SETTINGS_CONFIG_H "gtk-application-prefer-dark-theme", false, "gtk-enable-animations", false
|
||||
#define KEY(x) GDK_KEY_##x
|
||||
/*
|
||||
There are two different constants for Page_Up/Page_Down:
|
||||
This could possibly have something to so with having Page_Down/Page_Up
|
||||
as part of a keypad with/without NumLock
|
||||
See: https://docs.gtk.org/gdk3/?q=Page_Up
|
||||
See: https://docs.gtk.org/gdk3/?q=GDK_KEY_KP
|
||||
*/
|
||||
#define SFT 1 << 0
|
||||
#define CTRL 1 << 2
|
||||
#define ALT 1 << 3
|
||||
|
||||
// Shortcuts
|
||||
/* Misc helpers */
|
||||
#define ABORT_REQUEST_ON_CURRENT_TAB NULL
|
||||
#define NULLCHECK(x) \
|
||||
do { \
|
||||
if (x == NULL) { \
|
||||
printf("\nNULL check not passed"); \
|
||||
printf("@ %s (%d): ", __FILE__, __LINE__); \
|
||||
exit(0); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Shortcuts */
|
||||
typedef enum {
|
||||
goback,
|
||||
goforward,
|
||||
|
||||
refresh,
|
||||
refresh_force,
|
||||
|
||||
back_to_home,
|
||||
|
||||
toggle_fullscreen,
|
||||
toggle_custom_style,
|
||||
|
||||
zoomin,
|
||||
zoomout,
|
||||
zoom_reset,
|
||||
|
||||
new_tab,
|
||||
next_tab,
|
||||
prev_tab,
|
||||
close_tab,
|
||||
|
||||
show_searchbar,
|
||||
hide_bar,
|
||||
show_finder,
|
||||
finder_next,
|
||||
finder_prev,
|
||||
prettify,
|
||||
hide_bar
|
||||
} func;
|
||||
filter,
|
||||
|
||||
#define SFT 1 << 0
|
||||
#define CTRL 1 << 2
|
||||
#define ALT 1 << 3
|
||||
// reference: <https://github.com/GNOME/gtk/blob/7ea7d5c3906ccd231b04654101bb742f157d82f6/gdk/gdkenums.h#L140>
|
||||
halve_window,
|
||||
rebig_window,
|
||||
|
||||
prettify,
|
||||
save_uri_to_txt,
|
||||
open_uri_in_brave,
|
||||
} func;
|
||||
|
||||
static struct {
|
||||
unsigned mod;
|
||||
|
@ -96,47 +109,38 @@ static struct {
|
|||
} shortcut[] = {
|
||||
{ CTRL, KEY(h), goback },
|
||||
{ CTRL, KEY(j), goforward },
|
||||
|
||||
{ CTRL, KEY(r), refresh },
|
||||
{ CTRL, KEY(R), refresh_force },
|
||||
|
||||
{ CTRL, KEY(H), back_to_home },
|
||||
|
||||
{ 0x0, KEY(F11), toggle_fullscreen },
|
||||
{ CTRL, KEY(S), toggle_custom_style },
|
||||
|
||||
{ CTRL, KEY(equal), zoomin },
|
||||
{ CTRL, KEY(minus), zoomout },
|
||||
{ CTRL, KEY(0), zoom_reset },
|
||||
{ CTRL, KEY(KP_Page_Up), prev_tab }, /* also try KEY(Page_Up) if this doesn't work on your machine */
|
||||
{ CTRL, KEY(KP_Page_Down), next_tab }, /* ditto for KEY(Page_Down) */
|
||||
|
||||
{ CTRL | SFT, KEY(KP_Page_Up), prev_tab }, // use SFT just to show one can
|
||||
{ CTRL | SFT, KEY(KP_Page_Down), next_tab },
|
||||
{ CTRL | SFT, KEY(Page_Up), prev_tab },
|
||||
{ CTRL | SFT, KEY(Page_Down), next_tab },
|
||||
// working hypothesis: Page_UP vs KP_Page_Up might depend on whether the user has a numpad
|
||||
{ CTRL, KEY(Tab), next_tab },
|
||||
{ CTRL, KEY(b), prev_tab },
|
||||
{ CTRL, KEY(t), new_tab },
|
||||
{ CTRL, KEY(w), close_tab },
|
||||
{ 0x0, KEY(F11), toggle_fullscreen },
|
||||
|
||||
{ CTRL, KEY(l), show_searchbar },
|
||||
{ CTRL, KEY(semicolon), hide_bar },
|
||||
{ CTRL, KEY(o), hide_bar }, // previously: KEY(semicolon)
|
||||
{ CTRL, KEY(f), show_finder },
|
||||
{ CTRL, KEY(n), finder_next },
|
||||
{ CTRL, KEY(N), finder_prev },
|
||||
{ CTRL, KEY(p), prettify }
|
||||
{ CTRL, KEY(F), filter },
|
||||
{ CTRL, KEY(Up), halve_window },
|
||||
{ CTRL, KEY(Down), rebig_window },
|
||||
{ CTRL, KEY(p), prettify },
|
||||
{ CTRL, KEY(s), save_uri_to_txt },
|
||||
{ CTRL, KEY(b), open_uri_in_brave }
|
||||
};
|
||||
/* ^ For controls more akin to normal browsers */
|
||||
/* Reference for the key shorthand:
|
||||
* <https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/gdkkeysyms.h> */
|
||||
|
||||
/* Old controls: {
|
||||
{ CTRL, KEY(h), goback },
|
||||
{ CTRL, KEY(l), goforward },
|
||||
{ CTRL, KEY(r), refresh },
|
||||
{ CTRL | SFT, KEY(R), refresh_force },
|
||||
{ CTRL | SFT, KEY(H), back_to_home },
|
||||
{ CTRL, KEY(equal), zoomin },
|
||||
{ CTRL, KEY(minus), zoomout },
|
||||
{ CTRL, KEY(0), zoom_reset },
|
||||
{ ALT, KEY(h), prev_tab },
|
||||
{ CTRL, KEY(k), hide_searchbar },
|
||||
{ ALT, KEY(l), next_tab },
|
||||
{ CTRL, KEY(w), close_tab },
|
||||
{ 0x0, KEY(F11), toggle_fullscreen },
|
||||
{ CTRL, KEY(e), show_searchbar },
|
||||
{ CTRL, KEY(f), show_finder },
|
||||
{ CTRL, KEY(n), finder_next },
|
||||
{ CTRL | SFT, KEY(N), finder_prev },
|
||||
{ CTRL, KEY(p), prettify }
|
||||
};
|
||||
*/
|
||||
|
||||
|
|
93
makefile
93
makefile
|
@ -3,57 +3,60 @@ CC=gcc # alternatives: tcc, clang, zig cc
|
|||
WARNINGS=-Wall
|
||||
OPTIMIZED_SOME=-O3
|
||||
OPTIMIZED_MORE=-Ofast -march=native -funit-at-a-time -flto # binary will not be compatible with other computers, but may be much faster
|
||||
DEBUG= # -g
|
||||
DEBUG=#-g
|
||||
STD=-std=c99 # maybe consider moving to c11 and using safer string handling
|
||||
|
||||
# Dependencies
|
||||
DEPS='webkit2gtk-4.1'
|
||||
INCS=`pkg-config --cflags ${DEPS}`
|
||||
LIBS=`pkg-config --libs ${DEPS}`
|
||||
# Dependencies for WebkitGTK4/GTK3
|
||||
SRC_3=rosenrot3.c
|
||||
DEPS_3='webkit2gtk-4.1'
|
||||
INCS_3=`pkg-config --cflags ${DEPS_3}`
|
||||
LIBS_3=`pkg-config --libs ${DEPS_3}`
|
||||
|
||||
# Code
|
||||
SRC=rosenrot.c
|
||||
# Dependencies for WebkitGTK6/GTK4
|
||||
SRC_4=rosenrot4.c
|
||||
DEPS_4='webkitgtk-6.0'
|
||||
INCS_4=`pkg-config --cflags ${DEPS_4}` `pkg-config --cflags gtk4`
|
||||
LIBS_4=`pkg-config --libs ${DEPS_4}` `pkg-config --libs gtk4`
|
||||
DEPRECATION_FLAGS=-DGDK_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED
|
||||
|
||||
# User config
|
||||
CONFIG=config.h
|
||||
|
||||
# Plugins
|
||||
include plugins/plugins.mk
|
||||
# PLUGINS=./plugins/stand_in/stand_in.c
|
||||
ADBLOCK='-L/usr/lib/wyebrowser/adblock.so' # optional adblocking; depends on https://github.com/jun7/wyebadblock
|
||||
|
||||
## Formatter
|
||||
STYLE_BLUEPRINT="{BasedOnStyle: webkit, AllowShortIfStatementsOnASingleLine: true, IndentCaseLabels: true, AllowShortEnumsOnASingleLine: true}"
|
||||
FORMATTER=clang-format -i -style=$(STYLE_BLUEPRINT)
|
||||
FORMATTER_C=clang-format -i -style=$(STYLE_BLUEPRINT)
|
||||
FORMATTER_JS=npx prettier -w
|
||||
|
||||
# Runtime files
|
||||
MAINTAINER_CACHE_DIR=/home/nuno/.cache/rosenrot
|
||||
USER_CACHE_DIR=/home/`whoami`/.cache/rosenrot
|
||||
RUNTIME_FILES_DIR=/opt/rosenrot/
|
||||
|
||||
build: $(SRC) $(PLUGINS) $(CONFIG) constants user_cache
|
||||
$(CC) $(WARNINGS) $(OPTIMIZED_MORE) $(DEBUG) $(INCS) $(PLUGINS) $(SRC) -o rosenrot $(LIBS) $(ADBLOCK)
|
||||
|
||||
constants:
|
||||
@echo "# Computing constants"
|
||||
cd plugins/readability/ && sh recompute_READABILITY_N.sh
|
||||
cd plugins/style && sh recompute_STYLE_N.sh
|
||||
build: $(SRC_4) $(PLUGINS) $(CONFIG) constants user_cache
|
||||
$(CC) $(STD) $(WARNINGS) $(DEPRECATION_FLAGS) $(OPTIMIZED_MORE) $(DEBUG) $(INCS_4) $(PLUGINS) $(SRC_4) -o rosenrot $(LIBS_4) $(ADBLOCK)
|
||||
@echo
|
||||
|
||||
user_cache:
|
||||
@if [ `id -u` -eq 0 ]; then echo "can't run make user_cache with sudo, because USER_CACHE_DIR would be /home/root/.cache"; return 1; fi
|
||||
@echo "# Create user cache"
|
||||
mkdir -p $(USER_CACHE_DIR)
|
||||
find . -type f -not -path "*.git*" -not -path "*makefile*" -exec \
|
||||
sed -i "s|$(MAINTAINER_CACHE_DIR)|$(USER_CACHE_DIR)|g" {} +
|
||||
build3: $(SRC_3) $(PLUGINS) $(CONFIG) constants user_cache
|
||||
$(CC) $(STD) $(WARNINGS) $(OPTIMIZED_MORE) $(DEBUG) $(INCS_3) $(PLUGINS) $(SRC_3) -o rosenrot $(LIBS_3) $(ADBLOCK)
|
||||
@echo
|
||||
|
||||
runtime_files:
|
||||
sudo mkdir -p /opt/rosenrot/
|
||||
sudo cp style.css /opt/rosenrot/
|
||||
sudo cp -r images/flower-imgs /opt/rosenrot/
|
||||
sudo cp plugins/style/style.js /opt/rosenrot/
|
||||
sudo cp plugins/readability/readability.js /opt/rosenrot/
|
||||
format: $(SRC_3) $(SRC_4) $(PLUGINS)
|
||||
$(FORMATTER_C) $(SRC_3) $(PLUGINS) $(config.h)
|
||||
$(FORMATTER_C) $(SRC_4_greenfield) $(PLUGINS) $(config.h)
|
||||
$(FORMATTER_JS) plugins/readability/readability.js
|
||||
$(FORMATTER_JS) plugins/style/style.js
|
||||
|
||||
# Installation
|
||||
|
||||
install: rosenrot runtime_files
|
||||
cp -f rosenrot /usr/bin
|
||||
cp rosenrot-mklink /usr/bin
|
||||
@echo
|
||||
|
||||
uninstall:
|
||||
rm -r /opt/rosenrot
|
||||
|
@ -65,21 +68,45 @@ clean:
|
|||
rm rosenrot
|
||||
rm $(USER_CACHE_DIR)
|
||||
|
||||
format: $(SRC) $(PLUGINS)
|
||||
$(FORMATTER) $(SRC) $(PLUGINS) $(rosenrot.h)
|
||||
constants:
|
||||
@echo
|
||||
@echo "# Computing constants"
|
||||
cd plugins/readability/ && sh recompute_READABILITY_N.sh
|
||||
cd plugins/style && sh recompute_STYLE_N.sh
|
||||
@echo
|
||||
|
||||
user_cache:
|
||||
@if [ `id -u` -eq 0 ]; then echo "can't run make user_cache with sudo, because USER_CACHE_DIR would be /home/root/.cache"; return 1; fi
|
||||
@echo "# Create user cache"
|
||||
mkdir -p $(USER_CACHE_DIR)
|
||||
find . -type f -not -path "*.git*" -not -path "*makefile*" \
|
||||
-exec sed -i "s|$(MAINTAINER_CACHE_DIR)|$(USER_CACHE_DIR)|g" {} +
|
||||
@echo
|
||||
|
||||
runtime_files:
|
||||
@echo
|
||||
sudo mkdir -p /opt/rosenrot/
|
||||
sudo cp styles-gtk/style-gtk3.css /opt/rosenrot/
|
||||
sudo cp styles-gtk/style-gtk4.css /opt/rosenrot/
|
||||
sudo touch /opt/rosenrot/uris.txt
|
||||
sudo chmod a+rw /opt/rosenrot/uris.txt
|
||||
sudo cp -r images/flower-imgs /opt/rosenrot/
|
||||
sudo cp plugins/style/style.js /opt/rosenrot/
|
||||
sudo cp plugins/readability/readability.js /opt/rosenrot/
|
||||
|
||||
# More misc recipes
|
||||
|
||||
lint:
|
||||
clang-tidy $(SRC) $(PLUGINS) -- -Wall -O3 $(INCS) -o rosenrot $(LIBS)
|
||||
## A few more commands:
|
||||
|
||||
fast: $(SRC) $(PLUGINS) $(CONFIG)
|
||||
rm -f *.gcda
|
||||
GIO_MODULE_DIR=/usr/lib/x86_64-linux-gnu/gio/modules/
|
||||
$(CC) $(WARNINGS) $(OPTIMIZED_MORE) -fprofile-generate $(INCS) $(PLUGINS) $(SRC) -o rosenrot $(LIBS) $(ADBLOCK)
|
||||
$(CC) $(WARNINGS) $(OPTIMIZED_MORE) -fprofile-generate $(INCS_4) $(PLUGINS) $(SRC_4) -o rosenrot $(LIBS_4) $(ADBLOCK)
|
||||
@echo "Now use the browser for a while to gather some profiling data"
|
||||
sleep 2
|
||||
./rosenrot
|
||||
$(CC) $(WARNINGS) $(OPTIMIZED_MORE) -fprofile-use $(INCS) $(PLUGINS) $(SRC) -o rosenrot $(LIBS) $(ADBLOCK)
|
||||
$(CC) $(WARNINGS) $(OPTIMIZED_MORE) -fprofile-use $(INCS_4) $(PLUGINS) $(SRC_4) -o rosenrot $(LIBS_4) $(ADBLOCK)
|
||||
rm -f *.gcda
|
||||
|
||||
inspect: rosenrot
|
||||
|
@ -94,5 +121,3 @@ view-gtk3-version:
|
|||
twitter:
|
||||
sudo mkdir -p /usr/bin/rosenrot-browser
|
||||
sudo cp rosenrot /usr/bin/rosenrot-browser/twitter
|
||||
|
||||
# COMPILETIME_DEPRECATION_WARNINGS=#-DGDK_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED # turns out that webkit2gtk-4.1 is using some deprecated stuff, lol
|
||||
|
|
|
@ -17,26 +17,32 @@ int libre_redirect(const char* uri, char* output)
|
|||
} else {
|
||||
char* annoying_sites[] = {
|
||||
"https://www.reddit.com",
|
||||
"https://www.youtube.com",
|
||||
"https://google.com",
|
||||
"https://vitalik.ca",
|
||||
// "https://www.youtube.com",
|
||||
// "https://google.com",
|
||||
"https://medium.com",
|
||||
"https://translate.google.com",
|
||||
"https://www.bloomberg.com",
|
||||
"https://www.royalroad.com",
|
||||
"https://genius.com",
|
||||
// "https://twitter.com"
|
||||
// "https://archive.org",
|
||||
// "https://twitter.com"
|
||||
};
|
||||
|
||||
char* alternatives[] = {
|
||||
"https://old.reddit.com", // "https://teddit.nunosempere.com",
|
||||
"https://invidious.private.coffee",
|
||||
"https://search.nunosempere.com",
|
||||
"https://redlib.tux.pizza",
|
||||
// previously: "https://old.reddit.com", "https://teddit.nunosempere.com",
|
||||
// https://github.com/redlib-org/redlib-instances/blob/main/instances.md
|
||||
"https://vitalik.eth.limo",
|
||||
// "https://invidious.private.coffee",
|
||||
// "https://search.nunosempere.com",
|
||||
"https://scribe.rip",
|
||||
"https://translate.riverside.rocks",
|
||||
"https://archive.is/https://www.bloomberg.com",
|
||||
"https://archive.ph/https://www.bloomberg.com",
|
||||
"https://royalread.nunosempere.com",
|
||||
"https://dumb.vern.cc",
|
||||
"https://example.com"
|
||||
// "https://wayback.nunosempere.com",
|
||||
// "https://nitter.net"
|
||||
};
|
||||
|
@ -62,5 +68,7 @@ int libre_redirect(const char* uri, char* output)
|
|||
}
|
||||
strcpy(output, uri);
|
||||
}
|
||||
return 0;
|
||||
|
||||
int utm_check = str_destructively_omit_from(output, "?utm");
|
||||
return utm_check * 2;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ SHORTCUTS=./plugins/shortcuts/shortcuts.c
|
|||
READABILITY=./plugins/readability/readability.c
|
||||
LIBRE_REDIRECT=./plugins/libre_redirect/libre_redirect.c
|
||||
|
||||
ADBLOCK='-L/usr/lib/wyebrowser/adblock.so' # optional adblocking; depends on https://github.com/jun7/wyebadblock
|
||||
|
||||
STAND_IN=./plugins/stand_in/stand_in.c # gives function definitions for the above, which do nothing
|
||||
|
||||
PLUGINS=$(COMMON_CODE) $(CUSTOM_STYLES) $(SHORTCUTS) $(READABILITY) $(LIBRE_REDIRECT)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define READABILITY_N 88067 + 1000
|
||||
#define READABILITY_N 88212 + 1000
|
||||
|
||||
void read_readability_js(char* string)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#define READABILITY_N 88067 + 1000
|
||||
#define READABILITY_N 88212 + 1000
|
||||
|
||||
void read_readability_js(char* string);
|
||||
|
|
|
@ -32,7 +32,7 @@ function Readability(doc, options) {
|
|||
options = arguments[2];
|
||||
} else if (!doc || !doc.documentElement) {
|
||||
throw new Error(
|
||||
"First argument to Readability constructor should be a document object."
|
||||
"First argument to Readability constructor should be a document object.",
|
||||
);
|
||||
}
|
||||
options = options || {};
|
||||
|
@ -53,7 +53,7 @@ function Readability(doc, options) {
|
|||
options.nbTopCandidates || this.DEFAULT_N_TOP_CANDIDATES;
|
||||
this._charThreshold = options.charThreshold || this.DEFAULT_CHAR_THRESHOLD;
|
||||
this._classesToPreserve = this.CLASSES_TO_PRESERVE.concat(
|
||||
options.classesToPreserve || []
|
||||
options.classesToPreserve || [],
|
||||
);
|
||||
this._keepClasses = !!options.keepClasses;
|
||||
this._serializer =
|
||||
|
@ -405,7 +405,7 @@ Readability.prototype = {
|
|||
tagNames.map(function (tag) {
|
||||
var collection = node.getElementsByTagName(tag);
|
||||
return Array.isArray(collection) ? collection : Array.from(collection);
|
||||
})
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -517,7 +517,7 @@ Readability.prototype = {
|
|||
this.REGEXPS.srcsetUrl,
|
||||
function (_, p1, p2, p3) {
|
||||
return toAbsoluteURI(p1) + (p2 || "") + p3;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
media.setAttribute("srcset", newSrcset);
|
||||
|
@ -545,7 +545,7 @@ Readability.prototype = {
|
|||
for (var i = 0; i < node.attributes.length; i++) {
|
||||
child.setAttribute(
|
||||
node.attributes[i].name,
|
||||
node.attributes[i].value
|
||||
node.attributes[i].value,
|
||||
);
|
||||
}
|
||||
node.parentNode.replaceChild(child, node);
|
||||
|
@ -574,7 +574,7 @@ Readability.prototype = {
|
|||
// If they had an element with id "title" in their HTML
|
||||
if (typeof curTitle !== "string")
|
||||
curTitle = origTitle = this._getInnerText(
|
||||
doc.getElementsByTagName("title")[0]
|
||||
doc.getElementsByTagName("title")[0],
|
||||
);
|
||||
} catch (e) {
|
||||
/* ignore exceptions setting the title. */
|
||||
|
@ -599,7 +599,7 @@ Readability.prototype = {
|
|||
// could assume it's the full title.
|
||||
var headings = this._concatNodeLists(
|
||||
doc.getElementsByTagName("h1"),
|
||||
doc.getElementsByTagName("h2")
|
||||
doc.getElementsByTagName("h2"),
|
||||
);
|
||||
var trimmedTitle = curTitle.trim();
|
||||
var match = this._someNode(headings, function (heading) {
|
||||
|
@ -755,7 +755,7 @@ Readability.prototype = {
|
|||
try {
|
||||
replacement.setAttribute(
|
||||
node.attributes[i].name,
|
||||
node.attributes[i].value
|
||||
node.attributes[i].value,
|
||||
);
|
||||
} catch (ex) {
|
||||
/* it's possible for setAttribute() to throw if the attribute name
|
||||
|
@ -825,7 +825,7 @@ Readability.prototype = {
|
|||
// replace H1 with H2 as H1 should be only title that is displayed separately
|
||||
this._replaceNodeTags(
|
||||
this._getAllNodesWithTag(articleContent, ["h1"]),
|
||||
"h2"
|
||||
"h2",
|
||||
);
|
||||
|
||||
// Remove extra paragraphs
|
||||
|
@ -840,7 +840,7 @@ Readability.prototype = {
|
|||
var totalCount = imgCount + embedCount + objectCount + iframeCount;
|
||||
|
||||
return totalCount === 0 && !this._getInnerText(paragraph, false);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this._forEachNode(
|
||||
|
@ -848,7 +848,7 @@ Readability.prototype = {
|
|||
function (br) {
|
||||
var next = this._nextNode(br.nextSibling);
|
||||
if (next && next.tagName == "P") br.parentNode.removeChild(br);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Remove single-cell tables
|
||||
|
@ -866,12 +866,12 @@ Readability.prototype = {
|
|||
cell,
|
||||
this._everyNode(cell.childNodes, this._isPhrasingContent)
|
||||
? "P"
|
||||
: "DIV"
|
||||
: "DIV",
|
||||
);
|
||||
table.parentNode.replaceChild(cell, table);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -1032,7 +1032,7 @@ Readability.prototype = {
|
|||
while (true) {
|
||||
this.log("Starting grabArticle loop");
|
||||
var stripUnlikelyCandidates = this._flagIsActive(
|
||||
this.FLAG_STRIP_UNLIKELYS
|
||||
this.FLAG_STRIP_UNLIKELYS,
|
||||
);
|
||||
|
||||
// First, node prepping. Trash nodes that look cruddy (like ones with the
|
||||
|
@ -1066,7 +1066,7 @@ Readability.prototype = {
|
|||
this.log(
|
||||
"Removing header: ",
|
||||
node.textContent.trim(),
|
||||
this._articleTitle.trim()
|
||||
this._articleTitle.trim(),
|
||||
);
|
||||
shouldRemoveTitleHeader = false;
|
||||
node = this._removeAndGetNext(node);
|
||||
|
@ -1093,7 +1093,7 @@ Readability.prototype = {
|
|||
"Removing content with role " +
|
||||
node.getAttribute("role") +
|
||||
" - " +
|
||||
matchString
|
||||
matchString,
|
||||
);
|
||||
node = this._removeAndGetNext(node);
|
||||
continue;
|
||||
|
@ -1285,7 +1285,7 @@ Readability.prototype = {
|
|||
0.75
|
||||
) {
|
||||
alternativeCandidateAncestors.push(
|
||||
this._getNodeAncestors(topCandidates[i])
|
||||
this._getNodeAncestors(topCandidates[i]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1302,8 +1302,8 @@ Readability.prototype = {
|
|||
) {
|
||||
listsContainingThisAncestor += Number(
|
||||
alternativeCandidateAncestors[ancestorIndex].includes(
|
||||
parentOfTopCandidate
|
||||
)
|
||||
parentOfTopCandidate,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (listsContainingThisAncestor >= MINIMUM_TOPCANDIDATES) {
|
||||
|
@ -1367,7 +1367,7 @@ Readability.prototype = {
|
|||
|
||||
var siblingScoreThreshold = Math.max(
|
||||
10,
|
||||
topCandidate.readability.contentScore * 0.2
|
||||
topCandidate.readability.contentScore * 0.2,
|
||||
);
|
||||
// Keep potential top candidate's parent node to try to get text direction of it later.
|
||||
parentOfTopCandidate = topCandidate.parentNode;
|
||||
|
@ -1382,11 +1382,11 @@ Readability.prototype = {
|
|||
sibling,
|
||||
sibling.readability
|
||||
? "with score " + sibling.readability.contentScore
|
||||
: ""
|
||||
: "",
|
||||
);
|
||||
this.log(
|
||||
"Sibling has score",
|
||||
sibling.readability ? sibling.readability.contentScore : "Unknown"
|
||||
sibling.readability ? sibling.readability.contentScore : "Unknown",
|
||||
);
|
||||
|
||||
if (sibling === topCandidate) {
|
||||
|
@ -1529,7 +1529,7 @@ Readability.prototype = {
|
|||
if (parseSuccessful) {
|
||||
// Find out text direction from ancestors of final top candidate.
|
||||
var ancestors = [parentOfTopCandidate, topCandidate].concat(
|
||||
this._getNodeAncestors(parentOfTopCandidate)
|
||||
this._getNodeAncestors(parentOfTopCandidate),
|
||||
);
|
||||
this._someNode(ancestors, function (ancestor) {
|
||||
if (!ancestor.tagName) return false;
|
||||
|
@ -1582,7 +1582,7 @@ Readability.prototype = {
|
|||
function (_, hex, numStr) {
|
||||
var num = parseInt(hex || numStr, hex ? 16 : 10);
|
||||
return String.fromCharCode(num);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -1605,7 +1605,7 @@ Readability.prototype = {
|
|||
// Strip CDATA markers if present
|
||||
var content = jsonLdElement.textContent.replace(
|
||||
/^\s*<!\[CDATA\[|\]\]>\s*$/g,
|
||||
""
|
||||
"",
|
||||
);
|
||||
var parsed = JSON.parse(content);
|
||||
if (
|
||||
|
@ -1898,7 +1898,7 @@ Readability.prototype = {
|
|||
scriptNode.nodeValue = "";
|
||||
scriptNode.removeAttribute("src");
|
||||
return true;
|
||||
}
|
||||
},
|
||||
);
|
||||
this._removeNodes(this._getAllNodesWithTag(doc, ["noscript"]));
|
||||
},
|
||||
|
@ -2310,7 +2310,7 @@ Readability.prototype = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -2323,7 +2323,7 @@ Readability.prototype = {
|
|||
var children = this._getAllNodesWithTag(e, tags);
|
||||
this._forEachNode(
|
||||
children,
|
||||
(child) => (childrenLength += this._getInnerText(child, true).length)
|
||||
(child) => (childrenLength += this._getInnerText(child, true).length),
|
||||
);
|
||||
return childrenLength / textLength;
|
||||
},
|
||||
|
@ -2354,7 +2354,7 @@ Readability.prototype = {
|
|||
var listNodes = this._getAllNodesWithTag(node, ["ul", "ol"]);
|
||||
this._forEachNode(
|
||||
listNodes,
|
||||
(list) => (listLength += this._getInnerText(list).length)
|
||||
(list) => (listLength += this._getInnerText(list).length),
|
||||
);
|
||||
isList = listLength / this._getInnerText(node).length > 0.9;
|
||||
}
|
||||
|
@ -2554,7 +2554,7 @@ Readability.prototype = {
|
|||
var numTags = this._doc.getElementsByTagName("*").length;
|
||||
if (numTags > this._maxElemsToParse) {
|
||||
throw new Error(
|
||||
"Aborting parsing document; " + numTags + " elements found"
|
||||
"Aborting parsing document; " + numTags + " elements found",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2613,11 +2613,13 @@ if (typeof module === "object") {
|
|||
|
||||
var style_sheet_simple = `
|
||||
<style type="text/css">
|
||||
/* @import url("https://fonts.googleapis.com/css2?family=Inconsolata"); */
|
||||
|
||||
body {
|
||||
padding: 40px 200px 40px 200px !important;
|
||||
padding: 30px 150px 30px 150px !important;
|
||||
font-size: 18px;
|
||||
font: 18px/1.5 Roboto;
|
||||
// font: 18px/1.5 Roboto;
|
||||
// font-family: "Inconsolata";
|
||||
line-height: 1.6;
|
||||
background-color: #FEFEFE !important;
|
||||
color: #444 !important;
|
||||
|
|
|
@ -22,9 +22,11 @@ int shortcut_expand(const char* uri, char* output)
|
|||
"!blog",
|
||||
"!fnf",
|
||||
"!fnc",
|
||||
"!fs",
|
||||
"!hn",
|
||||
"!hnb"
|
||||
"!x",
|
||||
"!hnb",
|
||||
"!ww",
|
||||
"!x"
|
||||
};
|
||||
|
||||
char* expansions[] = {
|
||||
|
@ -32,9 +34,11 @@ int shortcut_expand(const char* uri, char* output)
|
|||
"https://nunosempere.com/blog",
|
||||
"https://forum.nunosempere.com/frontpage",
|
||||
"https://forum.nunosempere.com/comments",
|
||||
"https://forecasting.substack.com",
|
||||
"https://news.ycombinator.com",
|
||||
"https://news.ycombinator.com/best",
|
||||
"https://twitter.com",
|
||||
"https://web.whatsapp.com",
|
||||
"https://twitter.com"
|
||||
};
|
||||
|
||||
// len = sizeof(shortcuts) / sizeof(shortcuts[0]);
|
||||
|
|
|
@ -59,3 +59,26 @@ See also:
|
|||
* <https://web.archive.org/web/20160201212501/coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring>
|
||||
* https://github.com/irl/la-cucina/blob/master/str_replace.c
|
||||
*/
|
||||
|
||||
|
||||
int str_destructively_omit_from(char* input, const char* from){
|
||||
// input = "https://url.com/?utm=blah", from = "?utm"
|
||||
int l1 = strlen(input);
|
||||
int l2 = strlen(from);
|
||||
|
||||
for(int i=0; i<l1; i++){
|
||||
if((i + l2) > l1) { // no more room
|
||||
continue;
|
||||
}
|
||||
for(int j=0; j<l2; j++){
|
||||
if(input[i+j] != from[j]){
|
||||
goto cont;
|
||||
}
|
||||
}
|
||||
input[i]='\0';
|
||||
printf("Replaced utm %s\n", input);
|
||||
return 1;
|
||||
cont:;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
|
||||
void str_init(char* str, int n);
|
||||
int str_replace_start(const char* string, const char* target, const char* replacement, char* output);
|
||||
int str_destructively_omit_from(const char* input, const char* from);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define STYLE_N 7624 + 1000
|
||||
#define STYLE_N 9404 + 1000
|
||||
|
||||
void read_style_js(char* string)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#define STYLE_N 7624 + 1000
|
||||
#define STYLE_N 9404 + 1000
|
||||
|
||||
void read_style_js(char* string);
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
// Inspired by the Stylus app: <https://addons.mozilla.org/en-GB/firefox/addon/styl-us/>
|
||||
|
||||
// NOTE: This file is moved to /opt/rosenrot, so editing it here doesn't have direct effects on the runtime!!
|
||||
|
||||
// Main part of the code: switch on the domain and select the corresponding style
|
||||
var styles = null;
|
||||
// console.log(document.domain);
|
||||
switch (document.domain) {
|
||||
case "forum.effectivealtruism.org":
|
||||
styles = `
|
||||
|
@ -51,6 +54,16 @@ switch (document.domain) {
|
|||
body {
|
||||
zoom: 0.625 !important;
|
||||
}
|
||||
`;
|
||||
break;
|
||||
case "search.brave.com":
|
||||
styles = `
|
||||
.download-button,
|
||||
a[href^="https://brave.com/download/"], .download-cta,
|
||||
.example-searches
|
||||
{
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
break;
|
||||
case "search.nunosempere.com":
|
||||
|
@ -81,12 +94,15 @@ switch (document.domain) {
|
|||
.native-ad-container,
|
||||
.native-sidebar-ad,
|
||||
.premium-banner-outer,
|
||||
.promotedlink,
|
||||
.promoted
|
||||
{
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
break;
|
||||
case "twitter.com":
|
||||
case "x.com":
|
||||
styles = `
|
||||
/* hide promoted tweets */
|
||||
:has(meta[property="og:site_name"][content="Twitter"])
|
||||
|
@ -103,7 +119,7 @@ switch (document.domain) {
|
|||
display: none !important;
|
||||
}
|
||||
[data-testid^="sidebarColumn"] {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide DMs v2 */
|
||||
|
@ -149,17 +165,15 @@ switch (document.domain) {
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
/* No change of colors in hover */
|
||||
*:hover {
|
||||
/* background-color: white !important; */
|
||||
background-color: !important;
|
||||
transition: none !important;
|
||||
}*/
|
||||
/*
|
||||
No change of colors in hover: seemed like a good idea
|
||||
but it fucks up going back and forth
|
||||
*:hover {
|
||||
background-color: white !important;
|
||||
background-color: inherit !important;
|
||||
transition: none !important;
|
||||
}*/
|
||||
}
|
||||
*/
|
||||
/* Hide go to top button */
|
||||
[aria-label^="New posts are available. Push the period key to go to the them."]{
|
||||
display: none;
|
||||
|
@ -171,7 +185,10 @@ switch (document.domain) {
|
|||
}
|
||||
`;
|
||||
break;
|
||||
case "":
|
||||
break;
|
||||
default:
|
||||
console.log(`Domain: ${document.domain}`);
|
||||
console.log("No custom style");
|
||||
}
|
||||
|
||||
|
@ -243,7 +260,7 @@ window.alert = (message) => {
|
|||
};
|
||||
|
||||
// Extra: hide video players on twitter
|
||||
if (document.domain == "twitter.com") {
|
||||
if (document.domain == "twitter.com" || document.domain == "x.com") {
|
||||
// Function to hide the grandparent of video players
|
||||
// takes 0.014ms to run, so performance is not the concern here.
|
||||
// timed with console.time, console.timeEnd
|
||||
|
@ -283,4 +300,60 @@ if (document.domain == "twitter.com") {
|
|||
hideVideoPlayerGrandparent();
|
||||
}
|
||||
|
||||
document.body.style.visibility = "visible";
|
||||
// document.body.style.visibility = "visible";
|
||||
|
||||
// Add some code to filter out articles for Sentinel
|
||||
|
||||
function filterByKeyword(str) {
|
||||
// e.g., "keyword" (equivalent to "keyword, p, 1")
|
||||
// e.g., "keyword, div, 3"
|
||||
// might not work with level=0, but not sure why
|
||||
const args = str.split(", ");
|
||||
let keword = null;
|
||||
let selector = "p"; /* or "*" for all */
|
||||
let level = 1;
|
||||
if (args.length > 0) {
|
||||
keyword = args[0].trim();
|
||||
}
|
||||
if (args.length > 1) {
|
||||
selector = args[1].trim();
|
||||
}
|
||||
if (args.length > 2) {
|
||||
level = Number(args[2].trim());
|
||||
}
|
||||
console.log(keyword, selector, level);
|
||||
// Get all elements matching the selector
|
||||
const elements = document.querySelectorAll(selector);
|
||||
|
||||
// Convert NodeList to Array to use array methods
|
||||
const elementsArray = Array.from(elements);
|
||||
|
||||
// Filter elements containing the keyword
|
||||
const matchingElements = elementsArray.filter((element) =>
|
||||
element.textContent.toLowerCase().includes(keyword.toLowerCase()),
|
||||
);
|
||||
|
||||
// Remove parent of each matching element
|
||||
matchingElements.forEach((element) => {
|
||||
let ancestor = element; // Start with the current element
|
||||
// Loop to climb up the DOM tree according to the level required
|
||||
for (let i = 0; i < level && ancestor !== null; i++) {
|
||||
ancestor = ancestor.parentNode; // Move up in the DOM tree
|
||||
}
|
||||
if (ancestor) {
|
||||
ancestor.style.display = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var keywords = [
|
||||
"Sinwar",
|
||||
"fentanyl",
|
||||
"tanker",
|
||||
"Hasina",
|
||||
"blame",
|
||||
"victory plan",
|
||||
];
|
||||
for (let keyword of keywords) {
|
||||
filterByKeyword(keyword);
|
||||
}
|
||||
|
|
|
@ -6,19 +6,21 @@
|
|||
#include "config.h"
|
||||
#include "plugins/plugins.h"
|
||||
|
||||
/* Global declarations */
|
||||
/* Global variables */
|
||||
static GtkNotebook* notebook;
|
||||
static GtkWindow* window;
|
||||
typedef enum { _SEARCH, _FIND, _HIDDEN } Bar_entry_mode;
|
||||
static struct {
|
||||
GtkHeaderBar* widget;
|
||||
GtkEntry* line;
|
||||
GtkEntryBuffer* line_text;
|
||||
enum { _SEARCH, _FIND, _HIDDEN } entry_mode;
|
||||
Bar_entry_mode entry_mode;
|
||||
} bar;
|
||||
static int num_tabs = 0;
|
||||
static int custom_style_enabled = 1;
|
||||
|
||||
// Forward declarations
|
||||
void toggle_bar(GtkNotebook* notebook);
|
||||
/* Forward declarations */
|
||||
void toggle_bar(GtkNotebook* notebook, Bar_entry_mode mode);
|
||||
void notebook_create_new_tab(GtkNotebook* notebook, const char* uri);
|
||||
|
||||
/* Utils */
|
||||
|
@ -28,34 +30,42 @@ WebKitWebView* notebook_get_webview(GtkNotebook* notebook)
|
|||
}
|
||||
|
||||
/* Load content*/
|
||||
|
||||
void load_uri(WebKitWebView* view, const char* uri)
|
||||
{
|
||||
if (strlen(uri) == 0) {
|
||||
bool is_empty_uri = (strlen(uri) == 0);
|
||||
if (is_empty_uri) {
|
||||
webkit_web_view_load_uri(view, "");
|
||||
bar.entry_mode = _SEARCH;
|
||||
toggle_bar(notebook);
|
||||
} else if (g_str_has_prefix(uri, "http://") || g_str_has_prefix(uri, "https://") || g_str_has_prefix(uri, "file://") || g_str_has_prefix(uri, "about:")) {
|
||||
toggle_bar(notebook, _SEARCH);
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_direct_uri_prefix = g_str_has_prefix(uri, "http://") || g_str_has_prefix(uri, "https://") || g_str_has_prefix(uri, "file://") || g_str_has_prefix(uri, "about:");
|
||||
if (has_direct_uri_prefix){
|
||||
webkit_web_view_load_uri(view, uri);
|
||||
} else if (strstr(uri, ".com") || strstr(uri, ".org")) {
|
||||
char tmp[strlen("https://") + strlen(uri)];
|
||||
snprintf(tmp, sizeof(tmp), "https://%s", uri);
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_common_domain_extension = (strstr(uri, ".com") || strstr(uri, ".org"));
|
||||
if (has_common_domain_extension){
|
||||
char tmp[strlen("https://") + strlen(uri) + 1];
|
||||
snprintf(tmp, sizeof(tmp) + 1, "https://%s", uri);
|
||||
webkit_web_view_load_uri(view, tmp);
|
||||
} else {
|
||||
// Check for shortcuts
|
||||
return;
|
||||
}
|
||||
|
||||
int l = SHORTCUT_N + strlen(uri) + 1;
|
||||
char uri_expanded[l];
|
||||
str_init(uri_expanded, l);
|
||||
int check = shortcut_expand(uri, uri_expanded);
|
||||
if (check == 2) {
|
||||
bool has_shortcut = (check == 2);
|
||||
if (has_shortcut){
|
||||
webkit_web_view_load_uri(view, uri_expanded);
|
||||
} else {
|
||||
// Feed into search engine.
|
||||
return;
|
||||
}
|
||||
|
||||
char tmp[strlen(uri) + strlen(SEARCH)];
|
||||
snprintf(tmp, sizeof(tmp), SEARCH, uri);
|
||||
webkit_web_view_load_uri(view, tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Deal with new load or changed load */
|
||||
|
@ -72,7 +82,7 @@ void redirect_if_annoying(WebKitWebView* view, const char* uri)
|
|||
}
|
||||
void set_custom_style(WebKitWebView* view)
|
||||
{
|
||||
if (CUSTOM_STYLE_ENABLED) {
|
||||
if (custom_style_enabled) {
|
||||
char* style_js = malloc(STYLE_N + 1);
|
||||
read_style_js(style_js);
|
||||
webkit_web_view_evaluate_javascript(view, style_js, -1, NULL, "rosenrot-style-plugin", NULL, NULL, NULL);
|
||||
|
@ -83,7 +93,7 @@ void handle_signal_load_changed(WebKitWebView* self, WebKitLoadEvent load_event,
|
|||
GtkNotebook* notebook)
|
||||
{
|
||||
switch (load_event) {
|
||||
// see <https://webkitgtk.org/reference/webkit2gtk/2.5.1/WebKitWebView.html>
|
||||
// https://webkitgtk.org/reference/webkit2gtk/2.5.1/WebKitWebView.html
|
||||
case WEBKIT_LOAD_STARTED:
|
||||
case WEBKIT_LOAD_COMMITTED:
|
||||
set_custom_style(self);
|
||||
|
@ -91,6 +101,7 @@ void handle_signal_load_changed(WebKitWebView* self, WebKitLoadEvent load_event,
|
|||
redirect_if_annoying(self, webkit_web_view_get_uri(self));
|
||||
break;
|
||||
case WEBKIT_LOAD_FINISHED: {
|
||||
set_custom_style(self);
|
||||
/* Add gtk tab title */
|
||||
const char* webpage_title = webkit_web_view_get_title(self);
|
||||
const int max_length = 25;
|
||||
|
@ -106,12 +117,39 @@ void handle_signal_load_changed(WebKitWebView* self, WebKitLoadEvent load_event,
|
|||
}
|
||||
gtk_notebook_set_tab_label_text(notebook, GTK_WIDGET(self),
|
||||
webpage_title == NULL ? "—" : tab_title);
|
||||
// gtk_widget_hide(GTK_WIDGET(bar));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Create new tabs */
|
||||
/* New tabs */
|
||||
WebKitWebView* create_new_webview()
|
||||
{
|
||||
char* style;
|
||||
|
||||
WebKitSettings* settings = webkit_settings_new_with_settings(WEBKIT_DEFAULT_SETTINGS, NULL);
|
||||
if (CUSTOM_USER_AGENT) {
|
||||
webkit_settings_set_user_agent(
|
||||
settings,
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
|
||||
"like Gecko) Chrome/120.0.0.0 Safari/537.3");
|
||||
// https://www.useragents.me
|
||||
}
|
||||
WebKitWebContext* web_context = webkit_web_context_new_with_website_data_manager(webkit_website_data_manager_new(DATA_MANAGER_OPTS, NULL));
|
||||
WebKitUserContentManager* contentmanager = webkit_user_content_manager_new();
|
||||
|
||||
WebKitCookieManager* cookiemanager = webkit_web_context_get_cookie_manager(web_context);
|
||||
webkit_cookie_manager_set_persistent_storage(cookiemanager, DATA_DIR "/cookies.sqlite", WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
|
||||
webkit_cookie_manager_set_accept_policy(cookiemanager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
|
||||
|
||||
if (g_file_get_contents("~/opt/rosenrot/style.css", &style, NULL, NULL)) {
|
||||
webkit_user_content_manager_add_style_sheet(
|
||||
contentmanager, webkit_user_style_sheet_new(style, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL));
|
||||
}
|
||||
|
||||
WebKitWebView* view = g_object_new(WEBKIT_TYPE_WEB_VIEW, "settings", settings, "web-context", web_context, "user-content-manager", contentmanager, NULL);
|
||||
|
||||
return view;
|
||||
}
|
||||
GtkWidget* handle_signal_create_new_tab(WebKitWebView* self,
|
||||
WebKitNavigationAction* navigation_action,
|
||||
GtkNotebook* notebook)
|
||||
|
@ -125,46 +163,9 @@ GtkWidget* handle_signal_create_new_tab(WebKitWebView* self,
|
|||
} else {
|
||||
webkit_web_view_evaluate_javascript(self, "alert('Too many tabs, not opening a new one')", -1, NULL, "rosenrot-alert-numtabs", NULL, NULL, NULL);
|
||||
}
|
||||
return NULL;
|
||||
/*
|
||||
WebKitGTK documentation recommends returning the new webview.
|
||||
I imagine that this might allow e.g., to go back in a new tab
|
||||
or generally to keep track of history.
|
||||
However, this would require either modifying notebook_create_new_tab
|
||||
or duplicating its contents, for unclear gain.
|
||||
*/
|
||||
return ABORT_REQUEST_ON_CURRENT_TAB;
|
||||
}
|
||||
WebKitWebView* create_new_webview()
|
||||
{
|
||||
char* style;
|
||||
WebKitSettings* settings;
|
||||
WebKitWebContext* web_context;
|
||||
WebKitCookieManager* cookiemanager;
|
||||
WebKitUserContentManager* contentmanager;
|
||||
|
||||
settings = webkit_settings_new_with_settings(WEBKIT_DEFAULT_SETTINGS, NULL);
|
||||
if (CUSTOM_USER_AGENT) {
|
||||
webkit_settings_set_user_agent(
|
||||
settings,
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
|
||||
"like Gecko) Chrome/110.0.0.0 Safari/537.36");
|
||||
// See: <https://www.useragents.me/> for some common user agents
|
||||
}
|
||||
web_context = webkit_web_context_new_with_website_data_manager(webkit_website_data_manager_new(DATA_MANAGER_OPTS, NULL));
|
||||
contentmanager = webkit_user_content_manager_new();
|
||||
cookiemanager = webkit_web_context_get_cookie_manager(web_context);
|
||||
|
||||
webkit_cookie_manager_set_persistent_storage(cookiemanager, DATA_DIR "/cookies.sqlite", WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
|
||||
|
||||
webkit_cookie_manager_set_accept_policy(cookiemanager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
|
||||
|
||||
if (g_file_get_contents("~/.config/rose/style.css", &style, NULL, NULL)) {
|
||||
webkit_user_content_manager_add_style_sheet(
|
||||
contentmanager, webkit_user_style_sheet_new(style, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL));
|
||||
}
|
||||
|
||||
return g_object_new(WEBKIT_TYPE_WEB_VIEW, "settings", settings, "web-context", web_context, "user-content-manager", contentmanager, NULL);
|
||||
}
|
||||
void notebook_create_new_tab(GtkNotebook* notebook, const char* uri)
|
||||
{
|
||||
if (num_tabs < MAX_NUM_TABS || MAX_NUM_TABS == 0) {
|
||||
|
@ -183,7 +184,7 @@ void notebook_create_new_tab(GtkNotebook* notebook, const char* uri)
|
|||
|
||||
gtk_notebook_set_current_page(notebook, n);
|
||||
gtk_notebook_set_tab_label_text(notebook, GTK_WIDGET(view), "-");
|
||||
webkit_web_view_set_zoom_level(view, ZOOM);
|
||||
webkit_web_view_set_zoom_level(view, ZOOM_START_LEVEL);
|
||||
num_tabs += 1;
|
||||
} else {
|
||||
webkit_web_view_evaluate_javascript(notebook_get_webview(notebook), "alert('Too many tabs, not opening a new one')",
|
||||
|
@ -192,8 +193,9 @@ void notebook_create_new_tab(GtkNotebook* notebook, const char* uri)
|
|||
}
|
||||
|
||||
/* Top bar */
|
||||
void toggle_bar(GtkNotebook* notebook)
|
||||
void toggle_bar(GtkNotebook* notebook, Bar_entry_mode mode)
|
||||
{
|
||||
bar.entry_mode = mode;
|
||||
switch (bar.entry_mode) {
|
||||
case _SEARCH: {
|
||||
const char* url = webkit_web_view_get_uri(notebook_get_webview(notebook));
|
||||
|
@ -219,6 +221,7 @@ void toggle_bar(GtkNotebook* notebook)
|
|||
gtk_widget_hide(GTK_WIDGET(bar.widget));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle what happens when the user is on the bar and presses enter
|
||||
void handle_signal_bar_press_enter(GtkEntry* self, GtkNotebook* notebook)
|
||||
{
|
||||
|
@ -234,11 +237,10 @@ void handle_signal_bar_press_enter(GtkEntry* self, GtkNotebook* notebook)
|
|||
gtk_widget_hide(GTK_WIDGET(bar.widget));
|
||||
}
|
||||
|
||||
/* Handle shortcuts */
|
||||
// Act when a particular shortcut is detected
|
||||
/* Shortcuts */
|
||||
int handle_shortcut(func id, GtkNotebook* notebook)
|
||||
{
|
||||
static double zoom = ZOOM;
|
||||
static double zoom = ZOOM_START_LEVEL;
|
||||
static bool is_fullscreen = 0;
|
||||
|
||||
WebKitWebView* view = notebook_get_webview(notebook);
|
||||
|
@ -251,6 +253,9 @@ int handle_shortcut(func id, GtkNotebook* notebook)
|
|||
webkit_web_view_go_forward(view);
|
||||
break;
|
||||
|
||||
case toggle_custom_style: /* Ctrl s + Ctrl Shift R to reload */
|
||||
custom_style_enabled ^= 1;
|
||||
// fallthrough
|
||||
case refresh:
|
||||
webkit_web_view_reload(view);
|
||||
break;
|
||||
|
@ -264,19 +269,19 @@ int handle_shortcut(func id, GtkNotebook* notebook)
|
|||
|
||||
case zoomin:
|
||||
webkit_web_view_set_zoom_level(view,
|
||||
(zoom += ZOOM_VAL));
|
||||
(zoom += ZOOM_STEPSIZE));
|
||||
break;
|
||||
case zoomout:
|
||||
webkit_web_view_set_zoom_level(view,
|
||||
(zoom -= ZOOM_VAL));
|
||||
(zoom -= ZOOM_STEPSIZE));
|
||||
break;
|
||||
case zoom_reset:
|
||||
webkit_web_view_set_zoom_level(view,
|
||||
(zoom = ZOOM));
|
||||
(zoom = ZOOM_START_LEVEL));
|
||||
break;
|
||||
|
||||
case prev_tab:; // declarations aren't statements
|
||||
// <https://stackoverflow.com/questions/92396/why-cant-variables-be-declared-in-a-switch-statement>
|
||||
// https://stackoverflow.com/questions/92396/why-cant-variables-be-declared-in-a-switch-statement
|
||||
int n = gtk_notebook_get_n_pages(notebook);
|
||||
int k = gtk_notebook_get_current_page(notebook);
|
||||
int l = (n + k - 1) % n;
|
||||
|
@ -289,20 +294,18 @@ int handle_shortcut(func id, GtkNotebook* notebook)
|
|||
gtk_notebook_set_current_page(notebook, j);
|
||||
break;
|
||||
case close_tab:
|
||||
gtk_notebook_remove_page(notebook, gtk_notebook_get_current_page(notebook));
|
||||
num_tabs -= 1;
|
||||
|
||||
switch (gtk_notebook_get_n_pages(notebook)) {
|
||||
switch(num_tabs){
|
||||
case 0:
|
||||
exit(0);
|
||||
break;
|
||||
case 1:
|
||||
gtk_notebook_set_show_tabs(notebook, false);
|
||||
break;
|
||||
// fallthrough
|
||||
default:
|
||||
gtk_notebook_remove_page(notebook, gtk_notebook_get_current_page(notebook));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case toggle_fullscreen:
|
||||
if (is_fullscreen)
|
||||
gtk_window_unfullscreen(window);
|
||||
|
@ -310,14 +313,11 @@ int handle_shortcut(func id, GtkNotebook* notebook)
|
|||
gtk_window_fullscreen(window);
|
||||
is_fullscreen = !is_fullscreen;
|
||||
break;
|
||||
|
||||
case show_searchbar:
|
||||
bar.entry_mode = _SEARCH;
|
||||
toggle_bar(notebook);
|
||||
toggle_bar(notebook, _SEARCH);
|
||||
break;
|
||||
case show_finder:
|
||||
bar.entry_mode = _FIND;
|
||||
toggle_bar(notebook);
|
||||
toggle_bar(notebook, _FIND);
|
||||
break;
|
||||
|
||||
case finder_next:
|
||||
|
@ -330,13 +330,18 @@ int handle_shortcut(func id, GtkNotebook* notebook)
|
|||
case new_tab:
|
||||
notebook_create_new_tab(notebook, NULL);
|
||||
gtk_notebook_set_show_tabs(notebook, true);
|
||||
bar.entry_mode = _SEARCH;
|
||||
toggle_bar(notebook);
|
||||
toggle_bar(notebook, _SEARCH);
|
||||
break;
|
||||
|
||||
case hide_bar:
|
||||
bar.entry_mode = _HIDDEN;
|
||||
toggle_bar(notebook);
|
||||
toggle_bar(notebook, _HIDDEN);
|
||||
break;
|
||||
|
||||
case halve_window:
|
||||
gtk_window_resize(window, FULL_WIDTH/2, HEIGHT);
|
||||
break;
|
||||
case rebig_window:
|
||||
gtk_window_resize(window, FULL_WIDTH, HEIGHT);
|
||||
break;
|
||||
|
||||
case prettify: {
|
||||
|
@ -362,15 +367,9 @@ int handle_signal_keypress(void* self, GdkEvent* event, GtkNotebook* notebook)
|
|||
GdkModifierType event_state = 0;
|
||||
gdk_event_get_state(event, &event_state);
|
||||
|
||||
int debug_shortcuts = 0;
|
||||
if (debug_shortcuts) {
|
||||
if (0) {
|
||||
printf("Keypress state: %d\n", event_state);
|
||||
if (event_state & GDK_CONTROL_MASK) {
|
||||
printf("Keypress state is: CONTROL\n");
|
||||
}
|
||||
printf("Keypress value: %d\n", event_keyval);
|
||||
printf("PageUp: %d %d\n", KEY(Page_Up), GDK_KEY_KP_Page_Up);
|
||||
printf("PageDown: %d %d\n", KEY(Page_Down), GDK_KEY_KP_Page_Down);
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizeof(shortcut) / sizeof(shortcut[0]); i++)
|
||||
|
@ -380,47 +379,49 @@ int handle_signal_keypress(void* self, GdkEvent* event, GtkNotebook* notebook)
|
|||
If I wanted to bind button presses, like the extra button in the mouse,
|
||||
I would have to bind the button-press-event signal instead.
|
||||
Some links in case I go down that road:
|
||||
- <https://docs.gtk.org/gtk3/signal.Widget.button-press-event.html>
|
||||
- <https://docs.gtk.org/gdk3/union.Event.html>
|
||||
- https://docs.gtk.org/gtk3/signal.Widget.button-press-event.html
|
||||
- https://docs.gtk.org/gdk3/union.Event.html
|
||||
- https://docs.gtk.org/gdk3/struct.EventButton.html
|
||||
*/
|
||||
// This API is deprecated in GTK4 :(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
/* Initialize GTK in general */
|
||||
gtk_init(NULL, NULL); // <https://docs.gtk.org/gtk3/func.init.html>
|
||||
g_object_set(gtk_settings_get_default(), GTK_SETTINGS_CONFIG_H, NULL); // <https://docs.gtk.org/gobject/method.Object.set.html>
|
||||
gtk_init(NULL, NULL); // https://docs.gtk.org/gtk3/func.init.html
|
||||
g_object_set(gtk_settings_get_default(), GTK_SETTINGS_CONFIG_H, NULL); // https://docs.gtk.org/gobject/method.Object.set.html
|
||||
GtkCssProvider* css = gtk_css_provider_new();
|
||||
gtk_css_provider_load_from_path(css, "/opt/rosenrot/style.css", NULL);
|
||||
gtk_css_provider_load_from_path(css, "/opt/rosenrot/style-gtk3.css", NULL);
|
||||
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(css), 800);
|
||||
|
||||
/* Initialize GTK objects. These are declared as static globals at the top of this file */
|
||||
// Notebook
|
||||
notebook = GTK_NOTEBOOK(gtk_notebook_new());
|
||||
gtk_notebook_set_show_tabs(notebook, false);
|
||||
gtk_notebook_set_show_border(notebook, false);
|
||||
|
||||
// Window
|
||||
window = GTK_WINDOW(gtk_window_new(0));
|
||||
gtk_window_set_default_size(window, WIDTH, HEIGHT);
|
||||
g_signal_connect(window, "key-press-event", G_CALLBACK(handle_signal_keypress), notebook);
|
||||
g_signal_connect(window, "destroy", G_CALLBACK(exit), notebook);
|
||||
// Notebook
|
||||
notebook = GTK_NOTEBOOK(gtk_notebook_new());
|
||||
gtk_notebook_set_show_tabs(notebook, false);
|
||||
gtk_notebook_set_show_border(notebook, false);
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(notebook));
|
||||
|
||||
// Bar
|
||||
bar.line_text = GTK_ENTRY_BUFFER(gtk_entry_buffer_new("", 0));
|
||||
bar.line = GTK_ENTRY(gtk_entry_new_with_buffer(bar.line_text));
|
||||
gtk_entry_set_alignment(bar.line, 0.48);
|
||||
gtk_widget_set_size_request(GTK_WIDGET(bar.line), BAR_SIZE, -1);
|
||||
g_signal_connect(bar.line, "activate", G_CALLBACK(handle_signal_bar_press_enter), notebook);
|
||||
gtk_widget_set_size_request(GTK_WIDGET(bar.line), BAR_WIDTH, -1);
|
||||
|
||||
bar.widget = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_header_bar_set_custom_title(bar.widget, GTK_WIDGET(bar.line));
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(bar.widget));
|
||||
|
||||
// Signals
|
||||
g_signal_connect(window, "key-press-event", G_CALLBACK(handle_signal_keypress), notebook);
|
||||
g_signal_connect(window, "destroy", G_CALLBACK(exit), notebook);
|
||||
g_signal_connect(bar.line, "activate", G_CALLBACK(handle_signal_bar_press_enter), notebook);
|
||||
|
||||
|
||||
/* Load first tab */
|
||||
char* first_uri = argc > 1 ? argv[1] : HOME;
|
||||
notebook_create_new_tab(notebook, first_uri);
|
507
rosenrot4.c
Normal file
507
rosenrot4.c
Normal file
|
@ -0,0 +1,507 @@
|
|||
#include <gdk/gdk.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <webkit/webkit.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "plugins/plugins.h"
|
||||
|
||||
/* Global variables */
|
||||
static GtkNotebook* notebook;
|
||||
static GtkWindow* window;
|
||||
typedef enum { _SEARCH, _FIND, _FILTER, _HIDDEN } Bar_entry_mode;
|
||||
static struct {
|
||||
GtkHeaderBar* widget;
|
||||
GtkEntry* line;
|
||||
GtkEntryBuffer* line_text;
|
||||
Bar_entry_mode entry_mode;
|
||||
} bar;
|
||||
static int num_tabs = 0;
|
||||
static int custom_style_enabled = 1;
|
||||
|
||||
/* Forward declarations */
|
||||
void toggle_bar(GtkNotebook* notebook, Bar_entry_mode mode);
|
||||
void notebook_create_new_tab(GtkNotebook* notebook, const char* uri);
|
||||
static int handle_signal_keypress(void* self, int keyval, int keycode,
|
||||
GdkModifierType state, void* controller);
|
||||
|
||||
/* Utils */
|
||||
WebKitWebView* notebook_get_webview(GtkNotebook* notebook) /* TODO: Think through whether to pass global variables or not */
|
||||
{
|
||||
WebKitWebView* view = WEBKIT_WEB_VIEW(gtk_notebook_get_nth_page(notebook, gtk_notebook_get_current_page(notebook)));
|
||||
NULLCHECK(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
/* Load content */
|
||||
void load_uri(WebKitWebView* view, const char* uri)
|
||||
{
|
||||
bool is_empty_uri = (strlen(uri) == 0);
|
||||
if (is_empty_uri) {
|
||||
webkit_web_view_load_uri(view, "");
|
||||
toggle_bar(notebook, _SEARCH);
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_direct_uri_prefix = g_str_has_prefix(uri, "http://") || g_str_has_prefix(uri, "https://") || g_str_has_prefix(uri, "file://") || g_str_has_prefix(uri, "about:");
|
||||
if (has_direct_uri_prefix){
|
||||
webkit_web_view_load_uri(view, uri);
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_common_domain_extension = (strstr(uri, ".com") || strstr(uri, ".org"));
|
||||
if (has_common_domain_extension){
|
||||
char tmp[strlen("https://") + strlen(uri) + 1];
|
||||
snprintf(tmp, sizeof(tmp) + 1, "https://%s", uri);
|
||||
webkit_web_view_load_uri(view, tmp);
|
||||
return;
|
||||
}
|
||||
|
||||
int l = SHORTCUT_N + strlen(uri) + 1;
|
||||
char uri_expanded[l];
|
||||
str_init(uri_expanded, l);
|
||||
int check = shortcut_expand(uri, uri_expanded);
|
||||
bool has_shortcut = (check == 2);
|
||||
if (has_shortcut){
|
||||
webkit_web_view_load_uri(view, uri_expanded);
|
||||
return;
|
||||
}
|
||||
|
||||
char tmp[strlen(uri) + strlen(SEARCH)];
|
||||
snprintf(tmp, sizeof(tmp), SEARCH, uri);
|
||||
webkit_web_view_load_uri(view, tmp);
|
||||
}
|
||||
|
||||
/* Deal with new load or changed load */
|
||||
void redirect_if_annoying(WebKitWebView* view, const char* uri)
|
||||
{
|
||||
if (LIBRE_REDIRECT_ENABLED) {
|
||||
int l = LIBRE_N + strlen(uri) + 1;
|
||||
char uri_filtered[l];
|
||||
str_init(uri_filtered, l);
|
||||
|
||||
int check = libre_redirect(uri, uri_filtered);
|
||||
if (check == 2) webkit_web_view_load_uri(view, uri_filtered);
|
||||
}
|
||||
}
|
||||
void set_custom_style(WebKitWebView* view)
|
||||
{
|
||||
if (custom_style_enabled) {
|
||||
char* style_js = malloc(STYLE_N + 1);
|
||||
read_style_js(style_js);
|
||||
webkit_web_view_evaluate_javascript(view, style_js, -1, NULL, "rosenrot-style-plugin", NULL, NULL, NULL);
|
||||
free(style_js);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_signal_load_changed(WebKitWebView* self, WebKitLoadEvent load_event,
|
||||
GtkNotebook* notebook)
|
||||
{
|
||||
switch (load_event) {
|
||||
// https://webkitgtk.org/reference/webkit2gtk/2.5.1/WebKitWebView.html
|
||||
case WEBKIT_LOAD_STARTED:
|
||||
case WEBKIT_LOAD_COMMITTED:
|
||||
set_custom_style(self);
|
||||
case WEBKIT_LOAD_REDIRECTED:
|
||||
redirect_if_annoying(self, webkit_web_view_get_uri(self));
|
||||
break;
|
||||
case WEBKIT_LOAD_FINISHED: {
|
||||
set_custom_style(self);
|
||||
/* Add gtk tab title */
|
||||
const char* webpage_title = webkit_web_view_get_title(self);
|
||||
const int max_length = 25;
|
||||
char tab_title[max_length + 1];
|
||||
if (webpage_title != NULL) {
|
||||
for (int i = 0; i < (max_length); i++) {
|
||||
tab_title[i] = webpage_title[i];
|
||||
if (webpage_title[i] == '\0') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tab_title[max_length] = '\0';
|
||||
}
|
||||
gtk_notebook_set_tab_label_text(notebook, GTK_WIDGET(self),
|
||||
webpage_title == NULL ? "—" : tab_title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* New tabs */
|
||||
WebKitWebView* create_new_webview()
|
||||
{
|
||||
WebKitSettings* settings = webkit_settings_new_with_settings(WEBKIT_DEFAULT_SETTINGS, NULL);
|
||||
if (CUSTOM_USER_AGENT) {
|
||||
webkit_settings_set_user_agent(
|
||||
settings,
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
|
||||
"like Gecko) Chrome/120.0.0.0 Safari/537.3");
|
||||
// https://www.useragents.me
|
||||
}
|
||||
WebKitNetworkSession* network_session = webkit_network_session_new(DATA_DIR, DATA_DIR);
|
||||
WebKitUserContentManager* contentmanager = webkit_user_content_manager_new();
|
||||
WebKitCookieManager* cookiemanager = webkit_network_session_get_cookie_manager(network_session);
|
||||
webkit_cookie_manager_set_persistent_storage(cookiemanager, DATA_DIR "/cookies.sqlite", WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
|
||||
webkit_cookie_manager_set_accept_policy(cookiemanager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
|
||||
|
||||
WebKitWebView* view = g_object_new(WEBKIT_TYPE_WEB_VIEW, "settings", settings, "network-session", network_session, "user-content-manager", contentmanager, NULL);
|
||||
NULLCHECK(view);
|
||||
|
||||
GtkEventController* event_controller = gtk_event_controller_key_new();
|
||||
g_signal_connect(event_controller, "key-pressed", G_CALLBACK(handle_signal_keypress), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(view), event_controller);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
GtkWidget* handle_signal_create_new_tab(WebKitWebView* self,
|
||||
WebKitNavigationAction* navigation_action,
|
||||
GtkNotebook* notebook)
|
||||
{
|
||||
NULLCHECK(self);
|
||||
NULLCHECK(notebook);
|
||||
if (num_tabs < MAX_NUM_TABS || num_tabs == 0) {
|
||||
WebKitURIRequest* uri_request = webkit_navigation_action_get_request(navigation_action);
|
||||
const char* uri = webkit_uri_request_get_uri(uri_request);
|
||||
webkit_web_view_stop_loading(self);
|
||||
printf("Creating new window: %s\n", uri);
|
||||
notebook_create_new_tab(notebook, uri);
|
||||
gtk_notebook_set_show_tabs(notebook, true);
|
||||
} else {
|
||||
webkit_web_view_evaluate_javascript(self, "alert('Too many tabs, not opening a new one')", -1, NULL, "rosenrot-alert-numtabs", NULL, NULL, NULL);
|
||||
}
|
||||
return ABORT_REQUEST_ON_CURRENT_TAB;
|
||||
// Could also return GTK_WIDGET(self), in which case the new uri would also be loaded in the current webview. This could be interesting if I wanted to e.g., open an alternative frontend in a new tab
|
||||
}
|
||||
|
||||
void notebook_create_new_tab(GtkNotebook* notebook, const char* uri)
|
||||
{
|
||||
if (num_tabs < MAX_NUM_TABS || MAX_NUM_TABS == 0) {
|
||||
WebKitWebView* view = create_new_webview();
|
||||
NULLCHECK(view);
|
||||
|
||||
g_signal_connect(view, "load_changed", G_CALLBACK(handle_signal_load_changed), notebook);
|
||||
g_signal_connect(view, "create", G_CALLBACK(handle_signal_create_new_tab), notebook);
|
||||
|
||||
int n = gtk_notebook_append_page(notebook, GTK_WIDGET(view), NULL);
|
||||
gtk_notebook_set_tab_reorderable(notebook, GTK_WIDGET(view), true);
|
||||
NULLCHECK(window);
|
||||
NULLCHECK(bar.widget);
|
||||
gtk_widget_set_visible(GTK_WIDGET(window), 1);
|
||||
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
|
||||
load_uri(view, (uri) ? uri : HOME);
|
||||
|
||||
set_custom_style(view);
|
||||
|
||||
gtk_notebook_set_current_page(notebook, n);
|
||||
gtk_notebook_set_tab_label_text(notebook, GTK_WIDGET(view), "-");
|
||||
webkit_web_view_set_zoom_level(view, ZOOM_START_LEVEL);
|
||||
num_tabs += 1;
|
||||
} else {
|
||||
webkit_web_view_evaluate_javascript(notebook_get_webview(notebook), "alert('Too many tabs, not opening a new one')",
|
||||
-1, NULL, "rosenrot-alert-numtabs", NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Top bar */
|
||||
void toggle_bar(GtkNotebook* notebook, Bar_entry_mode mode)
|
||||
{
|
||||
bar.entry_mode = mode;
|
||||
switch (bar.entry_mode) {
|
||||
case _SEARCH: {
|
||||
const char* url = webkit_web_view_get_uri(notebook_get_webview(notebook));
|
||||
gtk_entry_set_placeholder_text(bar.line, "Search");
|
||||
gtk_entry_buffer_set_text(bar.line_text, url, strlen(url));
|
||||
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 1);
|
||||
gtk_window_set_focus(window, GTK_WIDGET(bar.line));
|
||||
break;
|
||||
}
|
||||
case _FIND: {
|
||||
const char* search_text = webkit_find_controller_get_search_text(
|
||||
webkit_web_view_get_find_controller(notebook_get_webview(notebook)));
|
||||
|
||||
if (search_text != NULL)
|
||||
gtk_entry_buffer_set_text(bar.line_text, search_text, strlen(search_text));
|
||||
|
||||
gtk_entry_set_placeholder_text(bar.line, "Find");
|
||||
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 1);
|
||||
gtk_window_set_focus(window, GTK_WIDGET(bar.line));
|
||||
break;
|
||||
}
|
||||
case _FILTER: {
|
||||
gtk_entry_set_placeholder_text(bar.line, "Filter");
|
||||
gtk_entry_buffer_set_text(bar.line_text, "", strlen(""));
|
||||
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 1);
|
||||
gtk_window_set_focus(window, GTK_WIDGET(bar.line));
|
||||
break;
|
||||
}
|
||||
case _HIDDEN:
|
||||
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle what happens when the user is on the bar and presses enter
|
||||
void handle_signal_bar_press_enter(GtkEntry* self, GtkNotebook* notebook) /* consider passing notebook as the data here? */
|
||||
{
|
||||
WebKitWebView* view = notebook_get_webview(notebook);
|
||||
const char* bar_line_text = gtk_entry_buffer_get_text(bar.line_text);
|
||||
switch (bar.entry_mode) {
|
||||
case _SEARCH: {
|
||||
load_uri(view, bar_line_text);
|
||||
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
|
||||
break;
|
||||
}
|
||||
case _FIND: {
|
||||
webkit_find_controller_search(
|
||||
webkit_web_view_get_find_controller(view),
|
||||
bar_line_text,
|
||||
WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | WEBKIT_FIND_OPTIONS_WRAP_AROUND,
|
||||
G_MAXUINT);
|
||||
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
|
||||
break;
|
||||
}
|
||||
case _FILTER: {
|
||||
const char* js_template = "filterByKeyword(\"%s\")";
|
||||
char js_command[strlen(js_template) + strlen(bar_line_text) + 2];
|
||||
snprintf(js_command, sizeof(js_command) + 1, js_template, bar_line_text);
|
||||
webkit_web_view_evaluate_javascript(view, js_command, -1, NULL, "rosenrot-filter-plugin", NULL, NULL, NULL);
|
||||
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
|
||||
|
||||
break;
|
||||
}
|
||||
case _HIDDEN:
|
||||
// no op
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Shortcuts */
|
||||
int handle_shortcut(func id)
|
||||
{
|
||||
static double zoom = ZOOM_START_LEVEL;
|
||||
static bool is_fullscreen = 0;
|
||||
|
||||
WebKitWebView* view = notebook_get_webview(notebook);
|
||||
NULLCHECK(notebook);
|
||||
NULLCHECK(view);
|
||||
|
||||
switch (id) {
|
||||
case goback:
|
||||
webkit_web_view_go_back(view);
|
||||
break;
|
||||
case goforward:
|
||||
webkit_web_view_go_forward(view);
|
||||
break;
|
||||
|
||||
case toggle_custom_style:
|
||||
custom_style_enabled ^= 1;
|
||||
// fallthrough
|
||||
case refresh:
|
||||
webkit_web_view_reload(view);
|
||||
break;
|
||||
case refresh_force:
|
||||
webkit_web_view_reload_bypass_cache(view);
|
||||
break;
|
||||
|
||||
case back_to_home:
|
||||
load_uri(view, HOME);
|
||||
break;
|
||||
|
||||
case zoomin:
|
||||
webkit_web_view_set_zoom_level(view,
|
||||
(zoom += ZOOM_STEPSIZE));
|
||||
break;
|
||||
case zoomout:
|
||||
webkit_web_view_set_zoom_level(view,
|
||||
(zoom -= ZOOM_STEPSIZE));
|
||||
break;
|
||||
case zoom_reset:
|
||||
webkit_web_view_set_zoom_level(view,
|
||||
(zoom = ZOOM_START_LEVEL));
|
||||
break;
|
||||
|
||||
case prev_tab:; // declarations aren't statements
|
||||
// https://stackoverflow.com/questions/92396/why-cant-variables-be-declared-in-a-switch-statement
|
||||
int n = gtk_notebook_get_n_pages(notebook);
|
||||
int k = gtk_notebook_get_current_page(notebook);
|
||||
int o = (n + k - 1) % n;
|
||||
gtk_notebook_set_current_page(notebook, o);
|
||||
break;
|
||||
case next_tab:;
|
||||
int m = gtk_notebook_get_n_pages(notebook);
|
||||
int l = gtk_notebook_get_current_page(notebook);
|
||||
int p = (l + 1) % m;
|
||||
gtk_notebook_set_current_page(notebook, p);
|
||||
break;
|
||||
case close_tab:
|
||||
num_tabs -= 1;
|
||||
switch(num_tabs){
|
||||
case 0:
|
||||
exit(0);
|
||||
break;
|
||||
case 1:
|
||||
gtk_notebook_set_show_tabs(notebook, false);
|
||||
// fallthrough
|
||||
default:
|
||||
gtk_notebook_remove_page(notebook, gtk_notebook_get_current_page(notebook));
|
||||
}
|
||||
break;
|
||||
case toggle_fullscreen:
|
||||
if (is_fullscreen)
|
||||
gtk_window_unfullscreen(window);
|
||||
else
|
||||
gtk_window_fullscreen(window);
|
||||
is_fullscreen = !is_fullscreen;
|
||||
break;
|
||||
case show_searchbar:
|
||||
toggle_bar(notebook, _SEARCH);
|
||||
break;
|
||||
case show_finder:
|
||||
toggle_bar(notebook, _FIND);
|
||||
break;
|
||||
case filter:
|
||||
toggle_bar(notebook, _FILTER);
|
||||
break;
|
||||
|
||||
case finder_next:
|
||||
webkit_find_controller_search_next(webkit_web_view_get_find_controller(view));
|
||||
break;
|
||||
case finder_prev:
|
||||
webkit_find_controller_search_previous(webkit_web_view_get_find_controller(view));
|
||||
break;
|
||||
|
||||
case new_tab:
|
||||
notebook_create_new_tab(notebook, NULL);
|
||||
gtk_notebook_set_show_tabs(notebook, true);
|
||||
toggle_bar(notebook, _SEARCH);
|
||||
break;
|
||||
|
||||
case hide_bar:
|
||||
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
|
||||
toggle_bar(notebook, _HIDDEN);
|
||||
break;
|
||||
|
||||
case halve_window:
|
||||
gtk_window_set_default_size(window, FULL_WIDTH/2, HEIGHT);
|
||||
break;
|
||||
case rebig_window:
|
||||
gtk_window_set_default_size(window, FULL_WIDTH, HEIGHT);
|
||||
break;
|
||||
|
||||
case prettify: {
|
||||
if (READABILITY_ENABLED) {
|
||||
char* readability_js = malloc(READABILITY_N + 1);
|
||||
read_readability_js(readability_js);
|
||||
webkit_web_view_evaluate_javascript(view, readability_js, -1, NULL, "rosenrot-readability-plugin", NULL, NULL, NULL);
|
||||
free(readability_js);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case save_uri_to_txt: {
|
||||
const char* uri = webkit_web_view_get_uri(view);
|
||||
FILE *f = fopen("/opt/rosenrot/uris.txt", "a");
|
||||
if (f == NULL) {
|
||||
printf("Error opening /opt/rosenrot/uris.txt");
|
||||
} else {
|
||||
fprintf(f, "%s\n", uri);
|
||||
fclose(f);
|
||||
webkit_web_view_evaluate_javascript(view, "alert('Saved current uri to /opt/rosenrot/uris.txt')", -1, NULL, "rosenrot-alert-numtabs", NULL, NULL, NULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case open_uri_in_brave: {
|
||||
const char* uri = webkit_web_view_get_uri(view);
|
||||
const char* brave_command = "brave-browser --app=%s --new-window --start-fullscreen &";
|
||||
char cmd[strlen(brave_command) + strlen(uri) + 2];
|
||||
snprintf(cmd, sizeof(cmd) + 1, brave_command, uri);
|
||||
system(cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Listen to keypresses */
|
||||
|
||||
static int handle_signal_keypress(void* self, int keyval, int keycode,
|
||||
GdkModifierType state, void* controller)
|
||||
{
|
||||
|
||||
if (0) {
|
||||
printf("New keypress\n");
|
||||
printf("Keypress state: %d\n", state);
|
||||
printf("Keypress value: %d\n", keyval);
|
||||
}
|
||||
for (int i = 0; i < sizeof(shortcut) / sizeof(shortcut[0]); i++) {
|
||||
if ((state & shortcut[i].mod || shortcut[i].mod == 0x0) && keyval == shortcut[i].key) {
|
||||
printf("New shortcut, with id: %d\n", shortcut[i].id);
|
||||
return handle_shortcut(shortcut[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// Initialize GTK in general
|
||||
gtk_init();
|
||||
g_object_set(gtk_settings_get_default(), GTK_SETTINGS_CONFIG_H, NULL);
|
||||
// https://docs.gtk.org/gobject/method.Object.set.html
|
||||
GtkCssProvider* css = gtk_css_provider_new();
|
||||
gtk_css_provider_load_from_path(css, "/opt/rosenrot/style-gtk4.css");
|
||||
gtk_style_context_add_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||
|
||||
// Create the main window
|
||||
window = GTK_WINDOW(gtk_window_new());
|
||||
gtk_window_set_default_size(window, WIDTH, HEIGHT);
|
||||
|
||||
// Set up notebook
|
||||
notebook = GTK_NOTEBOOK(gtk_notebook_new());
|
||||
gtk_notebook_set_show_tabs(notebook, false);
|
||||
gtk_notebook_set_show_border(notebook, false);
|
||||
gtk_window_set_child(window, GTK_WIDGET(notebook));
|
||||
|
||||
// Set up top bar
|
||||
bar.line_text = GTK_ENTRY_BUFFER(gtk_entry_buffer_new("", 0));
|
||||
bar.line = GTK_ENTRY(gtk_entry_new_with_buffer(bar.line_text));
|
||||
gtk_entry_set_alignment(bar.line, 0.5);
|
||||
gtk_widget_set_size_request(GTK_WIDGET(bar.line), BAR_WIDTH, -1);
|
||||
|
||||
bar.widget = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_header_bar_set_title_widget(bar.widget, GTK_WIDGET(bar.line));
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(bar.widget));
|
||||
|
||||
// Setup signals
|
||||
GtkEventController* event_controller = gtk_event_controller_key_new();
|
||||
g_signal_connect(event_controller, "key-pressed", G_CALLBACK(handle_signal_keypress), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(window), event_controller);
|
||||
|
||||
g_signal_connect(bar.line, "activate", G_CALLBACK(handle_signal_bar_press_enter), notebook);
|
||||
g_signal_connect(GTK_WIDGET(window), "destroy", G_CALLBACK(exit), notebook);
|
||||
|
||||
// Load first tab
|
||||
char* first_uri = argc > 1 ? argv[1] : HOME;
|
||||
notebook_create_new_tab(notebook, first_uri);
|
||||
|
||||
// Show to user
|
||||
// The first two commands are redundant with notebook_create_new_tab
|
||||
// gtk_window_present(window);
|
||||
// gtk_widget_set_visible(GTK_WIDGET(window), 1);
|
||||
if (argc != 0) gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
|
||||
|
||||
// Deal with more tabs, if any
|
||||
if (argc > 2) {
|
||||
gtk_notebook_set_show_tabs(notebook, true);
|
||||
for (int i = 2; i < argc; i++) {
|
||||
notebook_create_new_tab(notebook, argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Enter the main event loop, and wait for user interaction
|
||||
while (g_list_model_get_n_items(gtk_window_get_toplevels()) > 0 && num_tabs > 0)
|
||||
g_main_context_iteration(NULL, TRUE);
|
||||
|
||||
return 0;
|
||||
}
|
41
styles-gtk/style-gtk4-large.css
Normal file
41
styles-gtk/style-gtk4-large.css
Normal file
|
@ -0,0 +1,41 @@
|
|||
* {
|
||||
font-size: 35px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Make titlebar pretty gigantic. I'm pretty myopic. */
|
||||
.titlebar {
|
||||
padding: 5px;
|
||||
font-size: 45px;
|
||||
}
|
||||
|
||||
|
||||
.titlebar * {
|
||||
padding: 5px;
|
||||
font-size: 45px;
|
||||
}
|
||||
|
||||
header * {
|
||||
font-size: 40px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
tabs {
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
tab {
|
||||
margin: 5px 8px 5px 0px; /* top right bottom left */
|
||||
padding: 10px;
|
||||
border-style: solid;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
entry {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
entry:focus {
|
||||
padding-left: 10px;
|
||||
|
||||
}
|
41
styles-gtk/style-gtk4.css
Normal file
41
styles-gtk/style-gtk4.css
Normal file
|
@ -0,0 +1,41 @@
|
|||
* {
|
||||
font-size: 25px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Make titlebar pretty gigantic. I'm pretty myopic. */
|
||||
.titlebar {
|
||||
padding: 3px;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
|
||||
.titlebar * {
|
||||
padding: 3px;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
header * {
|
||||
font-size: 30px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
tabs {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
tab {
|
||||
margin: 3px 5px 3px 0px; /* top right bottom left */
|
||||
padding: 10px;
|
||||
border-style: solid;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
entry {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
entry:focus {
|
||||
padding-left: 10px;
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@ wget https://easylist.to/easylist/easylist.txt
|
|||
cd -
|
||||
|
||||
cd ../..
|
||||
make build # or just make
|
||||
make build3 # or just make
|
||||
sudo make install
|
||||
|
||||
cd -
|
42
user-scripts/debian-12/install-with-dependencies-gtk4.sh
Normal file
42
user-scripts/debian-12/install-with-dependencies-gtk4.sh
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Key dependencies
|
||||
sudo apt install libwebkitgtk-6.0-dev
|
||||
sudo apt install libgtk-4-dev
|
||||
|
||||
# Optional adblock
|
||||
git clone https://github.com/jun7/wyebadblock
|
||||
cd wyebadblock
|
||||
sudo apt install gstreamer1.0-plugins-good gstreamer1.0-libav
|
||||
WEBKITVER=4.1 make
|
||||
sudo WEBKITVER=4.1 make install
|
||||
|
||||
cd ..
|
||||
mkdir -p ~/.config/wyebadblock
|
||||
cd ~/.config/wyebadblock
|
||||
wget https://easylist.to/easylist/easylist.txt
|
||||
cd -
|
||||
|
||||
cd ../..
|
||||
make build4
|
||||
sudo make install
|
||||
|
||||
cd -
|
||||
# Debian desktop icon
|
||||
chmod +x rosenrot.desktop
|
||||
sudo cp rosenrot.desktop /usr/share/applications
|
||||
mkdir -p /opt/rosenrot
|
||||
sudo cp rosenrot-desktop-icon.png /opt/rosenrot/
|
||||
|
||||
# Optionally, set a shortcut (within GNOME)
|
||||
# https://askubuntu.com/questions/597395/how-to-set-custom-keyboard-shortcuts-from-terminal/1007035#1007035
|
||||
name="rose"
|
||||
binding="<CTRL><SHIFT>W"
|
||||
action="/usr/bin/rosenrot"
|
||||
|
||||
media_keys=org.gnome.settings-daemon.plugins.media-keys
|
||||
custom_kbd=org.gnome.settings-daemon.plugins.media-keys.custom-keybinding
|
||||
kbd_path=/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/$name/
|
||||
new_bindings=`gsettings get $media_keys custom-keybindings | sed -e"s>'\]>','$kbd_path']>"| sed -e"s>@as \[\]>['$kbd_path']>"`
|
||||
gsettings set $media_keys custom-keybindings "$new_bindings"
|
||||
gsettings set $custom_kbd:$kbd_path name "$name"
|
||||
gsettings set $custom_kbd:$kbd_path binding "$binding"
|
||||
gsettings set $custom_kbd:$kbd_path command "$action"
|
Loading…
Reference in New Issue
Block a user