Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e45fb881b | |||
| 00113a38f4 | |||
| 707b84492c | |||
| 14cae1987c | |||
| b3a0f14b2f | |||
| 4958d9eeaa | |||
| 4da5aad23e | |||
| dfa4b299a4 | |||
| f8ed7f9ef7 | |||
| c27525cc32 | |||
| 8790adb0fc | |||
| 350e6022df | |||
| ca5438c626 | |||
| cd6b158e3e | |||
| 31be29c7cb | |||
| 7fa7698265 | |||
| 0a7ab47224 | |||
| 38c5c0fb47 | |||
| f27979ab6f | |||
| 4e153dd592 | |||
| 246128e0c9 | |||
| 6ca97ba42f | |||
| fdb4ad7ced | |||
| 254b0ed658 | |||
| 5366c5d636 | |||
| 54fffb3b66 | |||
| a5394e8a88 | |||
| d3caecab58 | |||
| a6835ab2ae | |||
| 21e5800bbe | |||
| d25a6e7e79 | |||
| 618262d2a2 | |||
| 7c155525fd | |||
| 76ab73e38c | |||
| 304b4b274c | |||
| 9ca14fb3d4 | |||
| 892b3d3d41 | |||
| 29c07b9cbb | |||
| b2b3acf140 | |||
| 109d641017 | |||
| 4b2b46149c | |||
| 4dffbbae14 | |||
| 747499de2e | |||
| 0cbb1d97a9 | |||
| c9a81029da | |||
| a08755d8c9 | |||
| 0d9b05b7ec | |||
| c88b56d6c7 | |||
| f89ef9951f | |||
| fbf45c5c80 | |||
| 2f447f0b7b | |||
| 235e535008 | |||
| 19d8b63387 | |||
| efb0491d1b | |||
| bc3bb429bd | |||
| bebfbeb71c |
37
.clang-format
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# vim:ft=yaml
|
||||
|
||||
IndentWidth: 8
|
||||
BreakBeforeBraces: Linux
|
||||
UseTab: Always
|
||||
AlignArrayOfStructures: Left
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLoopsOnASingleLine: True
|
||||
AlwaysBreakBeforeMultilineStrings: True
|
||||
BreakBeforeTernaryOperators: True
|
||||
BreakStringLiterals: True
|
||||
ColumnLimit: 100
|
||||
IncludeBlocks: Regroup
|
||||
KeepEmptyLinesAtTheStartOfBlocks: True
|
||||
RemoveBracesLLVM: True
|
||||
SortIncludes: CaseInsensitive
|
||||
SortUsingDeclarations: True
|
||||
SpaceAfterLogicalNot: True
|
||||
SpaceAfterCStyleCast: True
|
||||
SpaceAfterTemplateKeyword: False
|
||||
PointerAlignment: Right
|
||||
SpaceBeforeAssignmentOperators: True
|
||||
SpaceBeforeCaseColon: False
|
||||
SpaceBeforeCpp11BracedList: True
|
||||
SpaceBeforeCtorInitializerColon: False
|
||||
SpaceBeforeInheritanceColon: False
|
||||
SpaceBeforeParens: Custom
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: True
|
||||
AfterForeachMacros: True
|
||||
AfterFunctionDeclarationName: False
|
||||
AfterFunctionDefinitionName: False
|
||||
AfterIfMacros: True
|
||||
AfterOverloadedOperator: False
|
||||
BeforeNonEmptyParentheses: False
|
||||
SpaceBeforeRangeBasedForLoopColon: True
|
||||
6
.gitignore
vendored
|
|
@ -1,3 +1,3 @@
|
|||
# don't save the binary file, as it doesn't play nicely with https://difftastic.wilfred.me.uk
|
||||
webkit/
|
||||
out/
|
||||
rose
|
||||
# config.h
|
||||
.clang-format
|
||||
|
|
|
|||
21
LICENSE.md
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022-2025 Nuño Sempere
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
136
README.md
|
|
@ -1,117 +1,37 @@
|
|||
# Rosenrot
|
||||
## Rosebud
|
||||
|
||||
Rosenrot is a small browser based on webkitgtk6/gtk4. Because of the ease of hackability, the minimalism, the unobstrusiveness, it gives me a more comfortable way of navigating the web than normal browsers.
|
||||
Personal tweaks for [rose](https://github.com/mini-rose/rose), a minimal browser based on webkit2gtk
|
||||
|
||||

|
||||
#### Features
|
||||
|
||||
---
|
||||
- tabs, cookies, caching
|
||||
- minimal ui, autohiding elements
|
||||
- ~400L code base (edit: no longer)
|
||||
- custom gtk and websites css
|
||||
- hackable without any knowledge
|
||||
- builtin rose-mklink script for in-shell static links
|
||||
- A few quality of life improvements.
|
||||
|
||||

|
||||
### 👐 Contribute
|
||||
This is my personal version. Contribute upstream to [github.com/mini-rose/rose](https://github.com/mini-rose/) instead.
|
||||
|
||||
---
|
||||
### To do
|
||||
|
||||

|
||||
- [ ] Find out what each of the css elements refers to.
|
||||
- [ ] Use something other than Whatsapp as an example syslink.
|
||||
|
||||
### Installation and usage
|
||||
Done:
|
||||
- [x] String substitution on uri in order to redirect to better frontends.
|
||||
- [x] Present "standard" browser keybindings as an alternative.
|
||||
- [x] Fix zoom in new tab
|
||||
- [x] Reader mode
|
||||
- [x] Add reader mode to config.def.
|
||||
- [x] Make tab bar slightly prettier.
|
||||
- [x] Add "open in new window" functionality.
|
||||
- Useful for opening links in new tab when clicking on them and selecting that option
|
||||
- And for actually opening links with the href newtab option.
|
||||
- Links: <https://docs.gtk.org/gobject/func.signal_connect.html>, <https://webkitgtk.org/reference/webkit2gtk/2.37.90/signal.AutomationSession.create-web-view.html>, <https://webkitgtk.org/reference/webkit2gtk/2.26.0/WebKitWebView.html#WebKitWebView-create> <https://stackoverflow.com/questions/40180757/webkit2gtk-get-new-window-link>
|
||||
|
||||
You can see detailed instructions [here](./user-scripts/debian-12/), for Debian 12/Ubuntu 24 and 25.
|
||||
|
||||
The general steps are to install dependencies, and then
|
||||
|
||||
```
|
||||
make build
|
||||
make install # or sudo make install
|
||||
rose
|
||||
```
|
||||
|
||||
You can also collect some profiling info, and then use that to get a perhaps faster version:
|
||||
|
||||
```
|
||||
make fast ## will ask you to use the browser for a bit
|
||||
make install
|
||||
rose
|
||||
```
|
||||
|
||||
You can also create a rose.desktop file so that it will show up in your desktop environment. You can see this documented [here](./user-scripts/debian-12/install-with-dependencies.sh).
|
||||
|
||||
## Features
|
||||
|
||||
- Tabs, cookies, caching
|
||||
- Minimal ui, autohiding elements
|
||||
- ~454L core code (the rose.c file)
|
||||
- Customize appearance of the browser through css
|
||||
- Built-in rose-mklink script for in-shell static links
|
||||
- Optional adblocking through [wyebadblock](https://github.com/jun7/wyebadblock)
|
||||
- Plugin system, seeded with:
|
||||
- Libre redirect: Redirect annoying websites to open source frontends
|
||||
- Readability: Strip webpages of unnecessary elements for ease of reading with a custom shortcut
|
||||
- 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
|
||||
- Fully GTK4 based, but see the git history for the GTK3 version as well.
|
||||
|
||||
You can see some screenshots in the [images](./images) folder.
|
||||
|
||||
## Similar projects
|
||||
|
||||
Here are some similar projects that I could find (minimalist, mostly based on webkit):
|
||||
|
||||
- [Surf](https://git.suckless.org/surf/). Suckless community. Similar goals, higher coding standards, less actively maintained.
|
||||
- [Rose](https://github.com/mini-rose/rose-browser). Lua integrations, supports compilation with GTK4. Every now and then, the developer nukes the git history and tries some different approach.
|
||||
- [Epiphany](https://gitlab.gnome.org/GNOME/epiphany). GNOME. Clean browser, distributed via flathub, aimed at nontechnical users. Seems actively maintained.
|
||||
- [Vimb](https://github.com/fanglingsu/vimb). Reasonably actively maintained, vim keybindings.
|
||||
- [Nyxt](https://github.com/atlas-engineer/nyxt). Emphasis on sophisticated key bindings.
|
||||
- [Wyeb](https://github.com/jun7/wyeb)
|
||||
- [Luakit](https://github.com/luakit/luakit)
|
||||
- ~~[Qutebrowser](https://github.com/qutebrowser/qutebrowser). More actively maintained. I don't understand the tech stack.~~ [Based](https://github.com/qutebrowser/qutebrowser/blob/main/doc/faq.asciidoc) on [Chromium](https://wiki.qt.io/QtWebEngine)
|
||||
|
||||
Here are other projects I haven't checked out as much: [netsurf](https://www.netsurf-browser.org/), [uzbl](https://www.uzbl.org/), [edbrowse](https://github.com/CMB/edbrowse),
|
||||
|
||||
Here are projects with their own rendering engines which could appeal to users of rosenrot:
|
||||
|
||||
- [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/LadybirdBrowser/ladybird). Initially from the SerenityOS, 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).
|
||||
- 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, and rosenrot now uses GTK4 and WebKitGTK 6.0.
|
||||
- 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)
|
||||
|
||||
- Surf is another browser based on GTK/Webkit, from the suckless community.
|
||||
- It is significantly more complex: surf.c has [2170](https://git.suckless.org/surf/file/surf.c.html) lines, vs rose.c's [454](https://git.nunosempere.com/open.source/rosenrot/src/branch/master/rose.c).
|
||||
- I find its code messier and harder to understand.
|
||||
- Conversely, surf has significantly more configuration options, and digs deeper into webkit internals.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
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 styles-gtk/style-gtk4.css file.
|
||||
- The gtk style usage isn't updated until installation. This is because by default rosenrot uses the theme located in /opt/rosenrot/style-gtk4.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:
|
||||
|
||||

|
||||
|
||||
### WebKitGTK and GTK4
|
||||
|
||||
Rosenrot uses webkit2gtk-6.0 and GTK4. See [this blog post](https://blogs.gnome.org/mcatanzaro/2023/03/21/webkitgtk-api-for-gtk-4-is-now-stable/) for details about WebKitGTK versions. Migration instructions for 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).
|
||||
|
||||
### 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.
|
||||
### Known bugs
|
||||
|
||||
- [ ] Doesn't work with when Spanish is selected as the language, for some reason.
|
||||
|
|
|
|||
74
ROADMAP.md
|
|
@ -1,74 +0,0 @@
|
|||
# To do
|
||||
|
||||
- [ ] Look into improving speed and performance:
|
||||
- [x] 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.
|
||||
- [x] Fix bug about distorted audio. Maybe related to [this pipewire issue](<https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/1547>)?
|
||||
- => This went away with time.
|
||||
- [x] 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
|
||||
- [x] Change README and point to last Ubuntu 20.04 commit
|
||||
- [x] Add list of similar projects: <https://github.com/qutebrowser/qutebrowser#similar-projects>
|
||||
- [x] Add comparisons against rose & surf
|
||||
- [x] Compare against rose
|
||||
- [x] Compare against surf
|
||||
- [x] ~~Doesn't work with when Spanish is selected as the language, for some reason~~ => Previously misdiagnosed. The real issue was that it freezes when interacting with [Espanso](https://espanso.org/) substitutions, which I had set-up automatically on my machine when using words containing an ñ, like my own name, Nuño.
|
||||
- [x] Add css for js alerts
|
||||
- [x] Add custom alert whose css can be customized
|
||||
- [ ] ~~Debug problems, e.g., this version is non-blocking.~~ => will leave as is
|
||||
- [x] Figure out better way to have plugins => stand_in code seems superfluous
|
||||
- [x] Double check newtab/next-tab behavior => custom style now loading correctly.
|
||||
- [x] Add a shortcut for hiding the search tab. => Already exists: Ctrl+K
|
||||
- [x] Find out what each of the css elements refers to. => done, see make inspect
|
||||
- [x] Figure out if downloading files is doable. => it is
|
||||
- [x] Look at using relative rather than absolute paths for configuration. => now makefile is a bit smarter
|
||||
- [x] Streamline installation a bit
|
||||
- [x] Substitute paths in makefile
|
||||
- [x] Create cache directory automatically
|
||||
- [x] Add an installation video walkthrough. Done, [here](https://video.nunosempere.com/w/t3oAvJLPHTSAMViQ6zbwTV)
|
||||
- [x] Document `stand_in.c` better
|
||||
- [x] Use a makefile.
|
||||
- [x] Add clean, uninstall to makefile
|
||||
- [x] Mask user agent
|
||||
- [x] Launch with more than one tab from command line
|
||||
- [x] Figure out merge with upstream
|
||||
- [x] String substitution on uri in order to redirect to better frontends.
|
||||
- [x] Present "standard" browser keybindings as an alternative.
|
||||
- [x] Fix zoom in new tab
|
||||
- [x] Reader mode
|
||||
- [x] Add reader mode to config.def.
|
||||
- [x] Make tab bar slightly prettier.
|
||||
- [x] Add "open in new window" functionality.
|
||||
- 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/
|
||||
16
build.sh
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
CC=clang
|
||||
SRC=rose.c
|
||||
REQS=./plugins/*/*.c
|
||||
DEPS=('webkit2gtk-4.0')
|
||||
DEBUG="" # set to "-g" for debug mode.
|
||||
|
||||
INCS=`pkg-config --cflags ${DEPS[@]}`
|
||||
LIBS=`pkg-config --libs ${DEPS[@]}`
|
||||
|
||||
# Optional adblocking depends on https://github.com/jun7/wyebadblock
|
||||
WYEBAB='-L/usr/lib/wyebrowser/adblock.so'
|
||||
|
||||
# echo $CC $INCS $LIBS $SRC $REQS $WYEBAB -o rose
|
||||
$CC $DEBUG $INCS $LIBS $SRC $REQS $WYEBAB -o rose
|
||||
109
config.def.h
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Nuño Sempere.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and any associated documentation
|
||||
* files to modify, copy, merge, publish, distribute and/or
|
||||
* sublicense copies of this sotware for their own use.
|
||||
* This code does not come with any warranty.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <gdk/gdkkeysyms.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* See more:
|
||||
* https://webkitgtk.org/reference/webkit2gtk/stable/class.Settings.html */
|
||||
#define WEBKIT \
|
||||
"enable-back-forward-navigation-gestures", true, "enable-developer-extras", true, \
|
||||
"enable-smooth-scrolling", false
|
||||
|
||||
#define GTK "gtk-application-prefer-dark-theme", false, "gtk-enable-animations", false
|
||||
#define ROSE_HOMEPAGE true
|
||||
#define HOME ROSE_HOMEPAGE ? "file:///home/loki/Documents/core/software/fresh/C/rose-browser/rose-bud-personal/user-scripts/ubuntu-20.04/rose-images/rose-homepage.png" : "https://lite.duckduckgo.com/html"
|
||||
#define SEARCH "https://lite.duckduckgo.com/html/?q=%s"
|
||||
#define CACHE_DIR "/home/your_username/.cache/rose"
|
||||
|
||||
#define WIDTH 1920
|
||||
#define HEIGHT 1080
|
||||
#define KEY(x) GDK_KEY_##x
|
||||
#define ZOOM 1.4 /* Starting zoom level.*/
|
||||
#define ZOOM_VAL .1 /* Zooming value in zoomin/zoomout functions */
|
||||
#define BG_COLOR "#FEFEFE" /* "#1E1E2E" */
|
||||
#define DEBUG false
|
||||
|
||||
typedef enum {
|
||||
goback,
|
||||
goforward,
|
||||
refresh,
|
||||
refresh_force,
|
||||
back_to_home,
|
||||
toggle_fullscreen,
|
||||
zoomin,
|
||||
zoomout,
|
||||
zoom_reset,
|
||||
next_tab,
|
||||
prev_tab,
|
||||
close_tab,
|
||||
show_searchbar,
|
||||
hide_searchbar,
|
||||
show_finder,
|
||||
finder_next,
|
||||
finder_prev,
|
||||
prettify
|
||||
} func;
|
||||
|
||||
#define SFT 1 << 0
|
||||
#define CTRL 1 << 2
|
||||
#define ALT 1 << 3
|
||||
|
||||
static struct {
|
||||
unsigned mod;
|
||||
unsigned key;
|
||||
func id;
|
||||
} keys[] = {
|
||||
{ CTRL, KEY(h), goback },
|
||||
{ CTRL, KEY(j), 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 },
|
||||
{ CTRL, KEY(Page_Down), prev_tab },
|
||||
{ CTRL, KEY(Page_Up), next_tab },
|
||||
{ CTRL, KEY(t), next_tab },
|
||||
{ CTRL, KEY(w), close_tab },
|
||||
{ 0x0, KEY(F11), toggle_fullscreen },
|
||||
{ CTRL, KEY(l), show_searchbar },
|
||||
{ CTRL, KEY(k), hide_searchbar },
|
||||
{ CTRL, KEY(f), show_finder },
|
||||
{ CTRL, KEY(n), finder_next },
|
||||
{ CTRL | SFT, KEY(N), finder_prev },
|
||||
{ CTRL, KEY(p), prettify }
|
||||
};
|
||||
/* ^ The controls above try to be similar to those in normal browsers */
|
||||
/* Reference for the key shorthand:
|
||||
* <https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/gdkkeysyms.h> */
|
||||
|
||||
/* Alternative 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 }
|
||||
};
|
||||
*/
|
||||
109
config.h
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Nuño Sempere.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and any associated documentation
|
||||
* files to modify, copy, merge, publish, distribute and/or
|
||||
* sublicense copies of this sotware for their own use.
|
||||
* This code does not come with any warranty.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <gdk/gdkkeysyms.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* See more:
|
||||
* https://webkitgtk.org/reference/webkit2gtk/stable/class.Settings.html */
|
||||
#define WEBKIT \
|
||||
"enable-back-forward-navigation-gestures", true, "enable-developer-extras", true, \
|
||||
"enable-smooth-scrolling", false
|
||||
|
||||
#define GTK "gtk-application-prefer-dark-theme", false, "gtk-enable-animations", false
|
||||
#define ROSE_HOMEPAGE true
|
||||
#define HOME ROSE_HOMEPAGE ? "file:///home/loki/Documents/core/software/fresh/C/rose-browser/rose-bud-personal/user-scripts/ubuntu-20.04/rose-images/rose-homepage.png" : "https://lite.duckduckgo.com/html"
|
||||
#define SEARCH "https://lite.duckduckgo.com/html/?q=%s"
|
||||
#define CACHE_DIR "/home/loki/.cache/rose"
|
||||
|
||||
#define WIDTH 1920
|
||||
#define HEIGHT 1080
|
||||
#define KEY(x) GDK_KEY_##x
|
||||
#define ZOOM 1.4 /* Starting zoom level.*/
|
||||
#define ZOOM_VAL .1 /* Zooming value in zoomin/zoomout functions */
|
||||
#define BG_COLOR "#FEFEFE" /* "FEFEFE", "#1E1E2E" */
|
||||
#define DEBUG false
|
||||
|
||||
typedef enum {
|
||||
goback,
|
||||
goforward,
|
||||
refresh,
|
||||
refresh_force,
|
||||
back_to_home,
|
||||
toggle_fullscreen,
|
||||
zoomin,
|
||||
zoomout,
|
||||
zoom_reset,
|
||||
next_tab,
|
||||
prev_tab,
|
||||
close_tab,
|
||||
show_searchbar,
|
||||
hide_searchbar,
|
||||
show_finder,
|
||||
finder_next,
|
||||
finder_prev,
|
||||
prettify
|
||||
} func;
|
||||
|
||||
#define SFT 1 << 0
|
||||
#define CTRL 1 << 2
|
||||
#define ALT 1 << 3
|
||||
|
||||
static struct {
|
||||
unsigned mod;
|
||||
unsigned key;
|
||||
func id;
|
||||
} keys[] = {
|
||||
{ CTRL, KEY(h), goback },
|
||||
{ CTRL, KEY(j), 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 },
|
||||
{ CTRL, KEY(Page_Down), prev_tab },
|
||||
{ CTRL, KEY(Page_Up), next_tab },
|
||||
{ CTRL, KEY(t), next_tab },
|
||||
{ CTRL, KEY(w), close_tab },
|
||||
{ 0x0, KEY(F11), toggle_fullscreen },
|
||||
{ CTRL, KEY(l), show_searchbar },
|
||||
{ CTRL, KEY(k), hide_searchbar },
|
||||
{ CTRL, KEY(f), show_finder },
|
||||
{ CTRL, KEY(n), finder_next },
|
||||
{ CTRL | SFT, KEY(N), finder_prev },
|
||||
{ CTRL, KEY(p), prettify }
|
||||
};
|
||||
/* ^ 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 }
|
||||
};
|
||||
*/
|
||||
8
install.sh
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
./plugins/*/*.sh
|
||||
|
||||
./build.sh && {
|
||||
cp -f rose /usr/bin
|
||||
mkdir -p /usr/share/themes/rose
|
||||
cp style.css /usr/share/themes/rose/
|
||||
cp rose-mklink /usr/bin
|
||||
}
|
||||
125
makefile
|
|
@ -1,125 +0,0 @@
|
|||
# C compiler
|
||||
CC=gcc # alternatives: tcc, clang, zig cc
|
||||
WARNINGS=-Wall -Wextra -Wshadow -Wformat=2 -Wno-unused-parameter
|
||||
WARNINGS_PEDANTIC=-Wpedantic
|
||||
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
|
||||
STD=-std=c99 # maybe consider moving to c11 and using safer string handling
|
||||
SECURITY=-fstack-protector-strong # detect stack buffer overflows at runtime
|
||||
|
||||
# Dependencies for WebkitGTK6/GTK4
|
||||
SRC=src/rosenrot.c
|
||||
DEPS='webkitgtk-6.0'
|
||||
INCS=`pkg-config --cflags ${DEPS}` `pkg-config --cflags gtk4`
|
||||
LIBS=`pkg-config --libs ${DEPS}` `pkg-config --libs gtk4`
|
||||
DEPRECATION_FLAGS=-DGDK_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED
|
||||
|
||||
# User config
|
||||
CONFIG=src/config.h
|
||||
|
||||
# Plugins
|
||||
include src/plugins/plugins.mk
|
||||
# PLUGINS=./src/plugins/stand_in/stand_in.c
|
||||
|
||||
# Adblock extension (built separately as a shared library)
|
||||
ADBLOCK_EXT_DIR=src/plugins/adblock
|
||||
ADBLOCK_EXT=$(ADBLOCK_EXT_DIR)/librosenrot-adblock.so
|
||||
|
||||
## Formatter
|
||||
STYLE_BLUEPRINT="{BasedOnStyle: webkit, AllowShortIfStatementsOnASingleLine: true, IndentCaseLabels: true, AllowShortEnumsOnASingleLine: true}"
|
||||
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) $(STD) $(WARNINGS) $(SECURITY) $(DEPRECATION_FLAGS) $(OPTIMIZED_MORE) $(DEBUG) $(INCS) $(PLUGINS) $(SRC) -o out/rosenrot $(LIBS)
|
||||
|
||||
build_adblock:
|
||||
@echo "# Building adblock extension"
|
||||
cd $(ADBLOCK_EXT_DIR) && $(MAKE)
|
||||
|
||||
build_all: build build_adblock
|
||||
@echo
|
||||
|
||||
format: $(SRC) $(PLUGINS)
|
||||
$(FORMATTER_C) $(SRC) $(PLUGINS) $(CONFIG)
|
||||
$(FORMATTER_JS) src/plugins/readability/readability.js
|
||||
$(FORMATTER_JS) src/plugins/style/style.js
|
||||
|
||||
# Installation
|
||||
|
||||
install: out/rosenrot runtime_files
|
||||
cp -f out/rosenrot /usr/bin/rosenrot
|
||||
@echo
|
||||
|
||||
uninstall:
|
||||
rm -rf /opt/rosenrot
|
||||
rm -f /usr/bin/rosenrot
|
||||
rm -rf $(USER_CACHE_DIR)
|
||||
|
||||
clean:
|
||||
rm -f out/rosenrot
|
||||
rm -rf $(USER_CACHE_DIR)
|
||||
|
||||
constants:
|
||||
@echo
|
||||
@echo "# Computing constants"
|
||||
cd src/plugins/readability/ && sh recompute_READABILITY_N.sh
|
||||
cd src/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 mkdir -p /opt/rosenrot/extensions/
|
||||
sudo cp src/styles-gtk/style-gtk4.css /opt/rosenrot/
|
||||
sudo touch /opt/rosenrot/uris.txt
|
||||
sudo chmod a+rw /opt/rosenrot/uris.txt
|
||||
sudo cp -r src/images/flower-imgs /opt/rosenrot/
|
||||
sudo cp src/plugins/style/style.js /opt/rosenrot/
|
||||
sudo cp src/plugins/readability/readability.js /opt/rosenrot/
|
||||
|
||||
install_adblock: build_adblock
|
||||
@echo "# Installing adblock extension"
|
||||
cd $(ADBLOCK_EXT_DIR) && sudo $(MAKE) install
|
||||
@echo "# Don't forget to download easylist.txt:"
|
||||
@echo "# curl -o /tmp/easylist.txt https://easylist.to/easylist/easylist.txt"
|
||||
@echo "# sudo mv /tmp/easylist.txt /opt/rosenrot/easylist.txt"
|
||||
|
||||
# More misc recipes
|
||||
|
||||
lint:
|
||||
clang-tidy $(SRC) $(PLUGINS) -- -Wall -O3 $(INCS) -o rosenrot $(LIBS)
|
||||
|
||||
fast: $(SRC) $(PLUGINS) $(CONFIG)
|
||||
rm -f *.gcda
|
||||
GIO_MODULE_DIR=/usr/lib/x86_64-linux-gnu/gio/modules/
|
||||
$(CC) $(WARNINGS) $(SECURITY) $(OPTIMIZED_MORE) -fprofile-generate $(INCS) $(PLUGINS) $(SRC) -o out/rosenrot $(LIBS) $(ADBLOCK)
|
||||
@echo "Now use the browser for a while to gather some profiling data"
|
||||
sleep 2
|
||||
./out/rosenrot
|
||||
$(CC) $(WARNINGS) $(SECURITY) $(OPTIMIZED_MORE) -fprofile-use $(INCS) $(PLUGINS) $(SRC) -o out/rosenrot $(LIBS) $(ADBLOCK)
|
||||
rm -f *.gcda
|
||||
|
||||
inspect: out/rosenrot
|
||||
GTK_DEBUG=interactive ./out/rosenrot
|
||||
|
||||
diagnose_deprecations: out/rosenrot
|
||||
G_ENABLE_DIAGNOSTIC=1 ./out/rosenrot
|
||||
|
||||
twitter:
|
||||
sudo mkdir -p /usr/bin/rosenrot-browser
|
||||
sudo cp out/rosenrot /usr/bin/rosenrot-browser/twitter
|
||||
3
plugins/.old/tranquility/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Taken from <https://raw.githubusercontent.com/ushnisha/tranquility-reader-webextensions/master/content_scripts/tranquilize.js>
|
||||
|
||||
The file has 46193 characters. Or 46194 including the line terminator.
|
||||
50
plugins/.old/tranquility/snippet.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
(() => {
|
||||
'use strict';
|
||||
|
||||
const addStyle = (() => {
|
||||
const parent = document.head || document.body || document.documentElement;
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
parent.appendChild(style);
|
||||
|
||||
return (css) => {
|
||||
style.appendChild(document.createTextNode(css + '\n'));
|
||||
};
|
||||
})();
|
||||
|
||||
Array.from(document.styleSheets, (css) => css.disabled = true);
|
||||
|
||||
// like a Firefox Reader View
|
||||
const nightmode = `
|
||||
* {
|
||||
margin-top: initial !important;
|
||||
padding-top: initial !important;
|
||||
}
|
||||
body {
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
max-width: 70% !important;
|
||||
font-size: 1.6em !important;
|
||||
line-height: 1.25em !important;
|
||||
background-color: #343A3A !important;
|
||||
color: #FDFDFD !important;
|
||||
white-space: pre-line !important;
|
||||
}
|
||||
h1 {
|
||||
line-height: 1em !important;
|
||||
}
|
||||
a {
|
||||
color: lightskyblue !important;
|
||||
background-color: initial !important;
|
||||
}
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
pre, code {
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
`;
|
||||
|
||||
addStyle(nightmode);
|
||||
})();
|
||||
BIN
plugins/.old/tranquility/tranquility
Executable file
30
plugins/.old/tranquility/tranquility.c
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#define TRANQUILITY_N 46288
|
||||
|
||||
void read_tranquility_js(char* string){
|
||||
FILE *fp=fopen("/home/loki/Documents/core/software/fresh/C/rose-browser/rose-bud-personal/plugins/tranquility/snippet.js", "r");
|
||||
if (!fp) { // fp is NULL, fopen failed
|
||||
fprintf(stderr, "Failed to open file\n");
|
||||
string=NULL;
|
||||
return;
|
||||
}
|
||||
int i=0;
|
||||
int c;
|
||||
while ((c = fgetc(fp)) != EOF){
|
||||
string[i++] = c;
|
||||
}
|
||||
string[i]='\0';
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/*
|
||||
int main(){
|
||||
char* tranquility_js = malloc(TRANQUILITY_N+1);
|
||||
read_tranquility_js(tranquility_js);
|
||||
printf("%s", tranquility_js);
|
||||
free(tranquility_js);
|
||||
|
||||
}
|
||||
*/
|
||||
8
plugins/.old/tranquility/tranquility.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef TRANQUILITY
|
||||
#define TRANQUILITY
|
||||
|
||||
#define TRANQUILITY_N 46288
|
||||
|
||||
void read_tranquility_js(char* string);
|
||||
|
||||
#endif
|
||||
1258
plugins/.old/tranquility/tranquilize.js
Normal file
71
plugins/libre_redirect/libre_redirect.c
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#include "str_replace_start.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#define LIBRE_N 19
|
||||
#define DEBUG false
|
||||
|
||||
/* Inspired by https://libredirect.github.io/, but in C. */
|
||||
|
||||
void str_init(char* str, int n){
|
||||
for(int i=0; i<n; i++){
|
||||
str[i] = ' ';
|
||||
}
|
||||
str[n] = '\0';
|
||||
} // could also use <https://manpages.ubuntu.com/manpages/impish/man3/strinit.3pub.html>
|
||||
|
||||
int libre_redirect(const char* uri, char* output){
|
||||
int l1 = strlen(uri);
|
||||
int l2 = strlen(output);
|
||||
|
||||
if((l2 - l1) < LIBRE_N){
|
||||
if(DEBUG) printf("Not enough memory\n");
|
||||
return 1; // not enough memory.
|
||||
}else{
|
||||
char tmp_uri[l2++];
|
||||
char tmp_output[l2++];
|
||||
strcpy(tmp_uri, uri); // strcpy also copies the terminating '\0'
|
||||
strcpy(tmp_output, output);
|
||||
|
||||
char* annoying_sites[] = {
|
||||
"https://www.youtube.com",
|
||||
"https://www.reddit.com",
|
||||
"https://medium.com",
|
||||
"https://translate.google.com",
|
||||
"https://forum.effectivealtruism.org",
|
||||
"https://www.bloomberg.com",
|
||||
"https://twitter.com",
|
||||
"https://wikipedia.org"
|
||||
};
|
||||
char* alternatives[] = {
|
||||
"https://invidious.flokinet.to", /* "https://yt.artemislena.eu", */
|
||||
"https://teddit.nunosempere.com",
|
||||
"https://scribe.rip",
|
||||
"https://simplytranslate.org/",
|
||||
"https://ea.greaterwrong.com",
|
||||
"https://archive.is/https://www.bloomberg.com",
|
||||
"https://nitter.net",
|
||||
"https://wikiless.org"
|
||||
};
|
||||
int n = sizeof(annoying_sites)/sizeof(annoying_sites[0]);
|
||||
for(int i=0; i<n ; i++){
|
||||
int replace_check = str_replace_start(tmp_uri, annoying_sites[i], alternatives[i], output);
|
||||
if(replace_check == 2){
|
||||
if(DEBUG) printf("tmp_uri: %s\n", tmp_uri);
|
||||
if(DEBUG) printf("output: %s\n", output);
|
||||
// strcpy(output, tmp_uri);
|
||||
// break;
|
||||
return 2;
|
||||
}else if(replace_check == 1){
|
||||
if(DEBUG) printf("replace_check failed\n");
|
||||
return 1;
|
||||
}
|
||||
strcpy(tmp_uri, output);
|
||||
str_init(output, l2);
|
||||
}
|
||||
strcpy(output, tmp_uri);
|
||||
}
|
||||
if(DEBUG) printf("No match found\n\n");
|
||||
return 0;
|
||||
|
||||
}
|
||||
9
plugins/libre_redirect/libre_redirect.h
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef LIBRE_REDIRECT
|
||||
#define LIBRE_REDIRECT
|
||||
|
||||
#define LIBRE_N 19
|
||||
|
||||
int libre_redirect(const char* uri, char* uri_filtered);
|
||||
void str_init(char* str, int n);
|
||||
|
||||
#endif
|
||||
60
plugins/libre_redirect/str_replace_start.c
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#define DEBUG false
|
||||
|
||||
/*
|
||||
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_replace_start(const char* string, const char* target, const char* replacement, char* output){
|
||||
|
||||
int l1 = strlen(string);
|
||||
int l2 = strlen(target);
|
||||
int l3 = strlen(replacement);
|
||||
int l4 = strlen(output);
|
||||
if(DEBUG) printf("%d,%d,%d,%d\n", l1, l2, l3, l4);
|
||||
// if(DEBUG) printf("%s,%s,%s,%s\n", string, target, replacement, output);
|
||||
|
||||
if((l4 < (l1 - l2 + l3)) || l4 < l1 ){
|
||||
// Not enough memory in output string.
|
||||
if(DEBUG) printf("String not long enough.\n");
|
||||
return 1;
|
||||
}
|
||||
/* else if(l1 < l2){
|
||||
// Not even possible that there is a match.
|
||||
if(DEBUG) printf("Target larger than string.\n");
|
||||
strcpy(output, string);
|
||||
} */
|
||||
else {
|
||||
if(DEBUG) printf("Looking for a match for %s in %s.\n", target, string);
|
||||
int match = true;
|
||||
for(int i=0; i<l2; i++){
|
||||
if(string[i] != target[i]){
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(match){
|
||||
if(DEBUG) printf("Found match.\n");
|
||||
for(int i=0; i<l3; i++){
|
||||
output[i] = replacement[i];
|
||||
}
|
||||
int counter = l3;
|
||||
for(int i=l2; i<l1; i++){
|
||||
output[counter] = string[i];
|
||||
counter++;
|
||||
}
|
||||
output[counter] = '\0';
|
||||
return 2; // success
|
||||
}
|
||||
else {
|
||||
if(DEBUG) printf("Did not find match.\n");
|
||||
strcpy(output, string);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
6
plugins/libre_redirect/str_replace_start.h
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef STR_REPLACE_H_
|
||||
#define STR_REPLACE_H_
|
||||
|
||||
int str_replace_start(const char* string, const char* target, const char* replacement, char* output);
|
||||
|
||||
#endif
|
||||
BIN
plugins/libre_redirect/str_replace_test/example
Executable file
20
plugins/libre_redirect/str_replace_test/example.c
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#include "../libre_redirect.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(){
|
||||
char uri[] = "https://reddit.com/r/blah";
|
||||
|
||||
int l = LIBRE_N + strlen(uri) + 1;
|
||||
char uri_filtered[l];
|
||||
str_init(uri_filtered, l);
|
||||
|
||||
if(!libre_redirect(uri, uri_filtered)){
|
||||
printf("Filtered uri: %s\n", uri_filtered);
|
||||
}else{
|
||||
printf("Uri: %s\n", uri);
|
||||
// failure; do something with the original uri.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
plugins/readability/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Taken from <https://raw.githubusercontent.com/ushnisha/readability-reader-webextensions/master/content_scripts/tranquilize.js>
|
||||
|
||||
The file has 46193 characters. Or 46194 including the line terminator.
|
||||
29
plugins/readability/readability.c
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#define READABILITY_N 84251 + 1
|
||||
|
||||
void read_readability_js(char* string){
|
||||
FILE *fp=fopen("/home/loki/Documents/core/software/fresh/C/rose-browser/rose-bud-personal/plugins/readability/readability.js", "r");
|
||||
if (!fp) { // fp is NULL, fopen failed
|
||||
fprintf(stderr, "Failed to open file\n");
|
||||
string=NULL;
|
||||
return;
|
||||
}
|
||||
int i=0;
|
||||
int c;
|
||||
while ((c = fgetc(fp)) != EOF){
|
||||
string[i++] = c;
|
||||
}
|
||||
string[i]='\0';
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/*
|
||||
int main(){
|
||||
char* readability_js = malloc(READABILITY_N+1);
|
||||
read_readability_js(readability_js);
|
||||
printf("%s", readability_js);
|
||||
free(readability_js);
|
||||
}
|
||||
*/
|
||||
8
plugins/readability/readability.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef READABILITY
|
||||
#define READABILITY
|
||||
|
||||
#define READABILITY_N 84251 + 1
|
||||
|
||||
void read_readability_js(char* string);
|
||||
|
||||
#endif
|
||||
8
plugins/readability/recompute_READABILITY_N.sh
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
function sedr(){
|
||||
find ./ -type f -exec sed -i -e "$1" {} \;
|
||||
} ## e.g., sedr "s/target/replacement/g"
|
||||
|
||||
READABILITY_N=$(wc -c ./plugins/*/readability.js | cut -d " " -f 1)
|
||||
sedr "s/^#define READABILITY_N .*/#define READABILITY_N $READABILITY_N + 1/g"
|
||||
|
||||
17
rose-mklink
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
|
||||
test "$1" = "--help" || test -z "$1" && {
|
||||
echo -e "usage: rose-mklink <alias> <url>\n"
|
||||
echo "Create a /usr/bin link to a website."
|
||||
exit
|
||||
}
|
||||
|
||||
test -z "$2" || {
|
||||
[ -f "/usr/bin/$1" ] && {
|
||||
echo "/usr/bin/$1 already exists, remove it first"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo -e "#!/bin/sh\n\nrose $2 > /dev/null &1>2 &" > /usr/bin/$1
|
||||
chmod +x /usr/bin/$1
|
||||
}
|
||||
414
rose.c
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Nuño Sempere.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and any associated documentation
|
||||
* files to modify, copy, merge, publish, distribute and/or
|
||||
* sublicense copies of this sotware for their own use.
|
||||
* This code does not come with any warranty.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "plugins/libre_redirect/libre_redirect.h"
|
||||
#include "plugins/readability/readability.h"
|
||||
|
||||
#define CACHE \
|
||||
"base-cache-directory", CACHE_DIR, \
|
||||
"base-data-directory", CACHE_DIR, \
|
||||
"disk-cache-directory", CACHE_DIR, \
|
||||
"dom-cache-directory", CACHE_DIR, \
|
||||
"hsts-cache-directory", CACHE_DIR, \
|
||||
"indexeddb-directory", CACHE_DIR, \
|
||||
"itp-directory", CACHE_DIR, \
|
||||
"local-storage-directory", CACHE_DIR, \
|
||||
"offline-application-cache-directory", CACHE_DIR, \
|
||||
"service-worker-registrations-directory", CACHE_DIR
|
||||
|
||||
enum { _SEARCH, _FIND };
|
||||
|
||||
static int entry_mode;
|
||||
static GtkWindow *window;
|
||||
static GtkHeaderBar *bar;
|
||||
static GtkEntryBuffer *search_buf;
|
||||
static GtkEntry *search;
|
||||
|
||||
WebKitWebView *webview_new()
|
||||
{
|
||||
char *style;
|
||||
WebKitSettings *settings;
|
||||
WebKitWebContext *web_context;
|
||||
WebKitCookieManager *cookiemanager;
|
||||
WebKitUserContentManager *contentmanager;
|
||||
|
||||
settings = webkit_settings_new_with_settings(WEBKIT, NULL);
|
||||
web_context = webkit_web_context_new_with_website_data_manager(
|
||||
webkit_website_data_manager_new(CACHE, NULL));
|
||||
contentmanager = webkit_user_content_manager_new();
|
||||
cookiemanager = webkit_web_context_get_cookie_manager(web_context);
|
||||
|
||||
webkit_cookie_manager_set_persistent_storage(cookiemanager, CACHE_DIR "/cookies.sqlite",
|
||||
WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
|
||||
|
||||
webkit_cookie_manager_set_accept_policy(cookiemanager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
|
||||
|
||||
webkit_web_context_set_process_model(web_context,
|
||||
WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
|
||||
|
||||
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 load_uri(WebKitWebView *view, const char *uri)
|
||||
{
|
||||
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:")) {
|
||||
webkit_web_view_load_uri(view, uri);
|
||||
} else {
|
||||
// webkit_web_view_load_uri(view, uri);
|
||||
char tmp[strlen(uri) + strlen(SEARCH)];
|
||||
snprintf(tmp, sizeof(tmp), SEARCH, uri);
|
||||
webkit_web_view_load_uri(view, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void redirect_if_annoying(WebKitWebView *view, const char *uri){
|
||||
int l = LIBRE_N + strlen(uri) + 1;
|
||||
char uri_filtered[l];
|
||||
str_init(uri_filtered, l);
|
||||
|
||||
if(DEBUG) printf("libre_redirect uri received : %s\n", uri);
|
||||
int check = libre_redirect(uri, uri_filtered);
|
||||
if(check == 2){
|
||||
webkit_web_view_load_uri(view, uri_filtered);
|
||||
}
|
||||
|
||||
if(DEBUG) printf("libre_redirect uri_filtered: %s\n", uri_filtered);
|
||||
if(DEBUG) printf("libre_redirect check: %d\n", check);
|
||||
}
|
||||
|
||||
void load_changed(WebKitWebView *self, WebKitLoadEvent load_event, GtkNotebook *notebook)
|
||||
{
|
||||
switch (load_event) {
|
||||
/* see <https://webkitgtk.org/reference/webkit2gtk/2.5.1/WebKitWebView.html> */
|
||||
case WEBKIT_LOAD_STARTED:
|
||||
redirect_if_annoying(self, webkit_web_view_get_uri(self));
|
||||
if(DEBUG) printf("Load started with uri: %s\n", webkit_web_view_get_uri (self));
|
||||
break;
|
||||
case WEBKIT_LOAD_REDIRECTED:
|
||||
redirect_if_annoying(self, webkit_web_view_get_uri(self));
|
||||
if(DEBUG) printf("Load redirected to uri: %s\n", webkit_web_view_get_uri (self));
|
||||
break;
|
||||
case WEBKIT_LOAD_COMMITTED:
|
||||
redirect_if_annoying(self, webkit_web_view_get_uri(self));
|
||||
if(DEBUG) printf("Load committed with: %s\n", webkit_web_view_get_uri (self));
|
||||
break;
|
||||
case WEBKIT_LOAD_FINISHED:
|
||||
{
|
||||
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 );
|
||||
// gtk_widget_hide(GTK_WIDGET(bar));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void notebook_append(GtkNotebook *notebook, const char *uri);
|
||||
/* notebook_append calls handle_create, but handle_create also calls notebook_append.
|
||||
* Therefore we need to declare notebook_append, so that handle_create_new_tab knows its type.
|
||||
*/
|
||||
|
||||
GtkWidget* handle_create_new_tab(WebKitWebView *self, WebKitNavigationAction *navigation_action, GtkNotebook *notebook){
|
||||
WebKitURIRequest *uri_request = webkit_navigation_action_get_request(navigation_action);
|
||||
const char *uri = webkit_uri_request_get_uri (uri_request);
|
||||
printf("Creating new window: %s\n", uri);
|
||||
notebook_append(notebook, uri);
|
||||
gtk_notebook_set_show_tabs(notebook, true);
|
||||
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_append
|
||||
* or duplicating its contents, for unclear gain.
|
||||
*/
|
||||
}
|
||||
|
||||
void notebook_append(GtkNotebook *notebook, const char *uri)
|
||||
{
|
||||
GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(window));
|
||||
GdkVisual *rgba_visual = gdk_screen_get_rgba_visual(screen);
|
||||
GdkRGBA rgba;
|
||||
|
||||
gdk_rgba_parse(&rgba, BG_COLOR);
|
||||
|
||||
WebKitWebView *view = webview_new();
|
||||
|
||||
gtk_widget_set_visual(GTK_WIDGET(window), rgba_visual);
|
||||
g_signal_connect(view, "load_changed", G_CALLBACK(load_changed), notebook);
|
||||
g_signal_connect(view, "create", G_CALLBACK(handle_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);
|
||||
gtk_widget_show_all(GTK_WIDGET(window));
|
||||
gtk_widget_hide(GTK_WIDGET(bar));
|
||||
webkit_web_view_set_background_color(view, &rgba);
|
||||
load_uri(view, (uri) ? uri : HOME);
|
||||
gtk_notebook_set_current_page(notebook, n);
|
||||
gtk_notebook_set_tab_label_text(notebook, GTK_WIDGET(view), "-" );
|
||||
}
|
||||
|
||||
WebKitWebView *notebook_get_webview(GtkNotebook *notebook)
|
||||
{
|
||||
return WEBKIT_WEB_VIEW(
|
||||
gtk_notebook_get_nth_page(notebook, gtk_notebook_get_current_page(notebook)));
|
||||
}
|
||||
|
||||
void show_bar(GtkNotebook *notebook)
|
||||
{
|
||||
if (entry_mode == _SEARCH) {
|
||||
const char *url = webkit_web_view_get_uri(notebook_get_webview(notebook));
|
||||
gtk_entry_set_placeholder_text(search, "Search");
|
||||
gtk_entry_buffer_set_text(search_buf, url, strlen(url));
|
||||
gtk_widget_show(GTK_WIDGET(bar));
|
||||
gtk_window_set_focus(window, GTK_WIDGET(search));
|
||||
} else {
|
||||
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(search_buf, search_text, strlen(search_text));
|
||||
gtk_entry_set_placeholder_text(search, "Find");
|
||||
gtk_widget_show(GTK_WIDGET(bar));
|
||||
gtk_window_set_focus(window, GTK_WIDGET(search));
|
||||
}
|
||||
}
|
||||
|
||||
int handle_key(func id, GtkNotebook *notebook)
|
||||
{
|
||||
static double zoom = ZOOM;
|
||||
static bool is_fullscreen = 0;
|
||||
|
||||
switch (id) {
|
||||
case goback:
|
||||
webkit_web_view_go_back(notebook_get_webview(notebook));
|
||||
break;
|
||||
case goforward:
|
||||
webkit_web_view_go_forward(notebook_get_webview(notebook));
|
||||
break;
|
||||
|
||||
case refresh:
|
||||
webkit_web_view_reload(notebook_get_webview(notebook));
|
||||
break;
|
||||
case refresh_force:
|
||||
webkit_web_view_reload_bypass_cache(notebook_get_webview(notebook));
|
||||
break;
|
||||
|
||||
case back_to_home:
|
||||
load_uri(notebook_get_webview(notebook), HOME);
|
||||
break;
|
||||
|
||||
case zoomin:
|
||||
webkit_web_view_set_zoom_level(notebook_get_webview(notebook), (zoom += ZOOM_VAL));
|
||||
break;
|
||||
|
||||
case zoomout:
|
||||
webkit_web_view_set_zoom_level(notebook_get_webview(notebook), (zoom -= ZOOM_VAL));
|
||||
break;
|
||||
|
||||
case zoom_reset:
|
||||
webkit_web_view_set_zoom_level(notebook_get_webview(notebook), (zoom = ZOOM));
|
||||
break;
|
||||
|
||||
case prev_tab:
|
||||
if (gtk_notebook_get_current_page(notebook) == 0) {
|
||||
gtk_notebook_set_current_page(notebook,
|
||||
gtk_notebook_get_n_pages(notebook) - 1);
|
||||
} else {
|
||||
gtk_notebook_prev_page(notebook);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case next_tab:
|
||||
if (gtk_notebook_get_current_page(notebook) ==
|
||||
gtk_notebook_get_n_pages(notebook) - 1) {
|
||||
notebook_append(notebook, NULL);
|
||||
gtk_notebook_set_show_tabs(notebook, true);
|
||||
webkit_web_view_set_zoom_level(notebook_get_webview(notebook), zoom);
|
||||
} else {
|
||||
gtk_notebook_next_page(notebook);
|
||||
}
|
||||
entry_mode = _SEARCH;
|
||||
show_bar(notebook);
|
||||
break;
|
||||
|
||||
case close_tab:
|
||||
gtk_notebook_remove_page(notebook, gtk_notebook_get_current_page(notebook));
|
||||
|
||||
switch (gtk_notebook_get_n_pages(notebook)) {
|
||||
case 0:
|
||||
exit(0);
|
||||
break;
|
||||
case 1:
|
||||
gtk_notebook_set_show_tabs(notebook, false);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case toggle_fullscreen:
|
||||
if (is_fullscreen)
|
||||
gtk_window_unfullscreen(window);
|
||||
else
|
||||
gtk_window_fullscreen(window);
|
||||
|
||||
is_fullscreen = ! is_fullscreen;
|
||||
break;
|
||||
|
||||
case show_searchbar:
|
||||
entry_mode = _SEARCH;
|
||||
show_bar(notebook);
|
||||
break;
|
||||
|
||||
case hide_searchbar:
|
||||
gtk_widget_hide(GTK_WIDGET(bar));
|
||||
break;
|
||||
|
||||
case show_finder:
|
||||
entry_mode = _FIND;
|
||||
show_bar(notebook);
|
||||
break;
|
||||
|
||||
case finder_next:
|
||||
webkit_find_controller_search_next(
|
||||
webkit_web_view_get_find_controller(notebook_get_webview(notebook)));
|
||||
break;
|
||||
|
||||
case finder_prev:
|
||||
webkit_find_controller_search_previous(
|
||||
webkit_web_view_get_find_controller(notebook_get_webview(notebook)));
|
||||
break;
|
||||
case prettify:{
|
||||
if(DEBUG){
|
||||
printf("prettify\n");
|
||||
webkit_web_view_run_javascript(notebook_get_webview(notebook),
|
||||
"alert('Tranquilizing!')",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
char* readability_js = malloc(READABILITY_N+1);
|
||||
read_readability_js(readability_js);
|
||||
webkit_web_view_run_javascript(notebook_get_webview(notebook),
|
||||
readability_js,
|
||||
NULL, NULL, NULL);
|
||||
free(readability_js);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int keypress(void *self, GdkEvent *e, GtkNotebook *notebook)
|
||||
{
|
||||
(void) self;
|
||||
|
||||
for (int i = 0; i < sizeof(keys) / sizeof(keys[0]); i++)
|
||||
if (e->key.keyval == keys[i].key && e->key.state == keys[i].mod)
|
||||
return handle_key(keys[i].id, notebook);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void search_activate(GtkEntry *self, GtkNotebook *notebook)
|
||||
{
|
||||
if (entry_mode == _SEARCH)
|
||||
load_uri(notebook_get_webview(notebook), gtk_entry_buffer_get_text(search_buf));
|
||||
else if (entry_mode == _FIND)
|
||||
webkit_find_controller_search(
|
||||
webkit_web_view_get_find_controller(notebook_get_webview(notebook)),
|
||||
gtk_entry_buffer_get_text(search_buf),
|
||||
WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | WEBKIT_FIND_OPTIONS_WRAP_AROUND,
|
||||
G_MAXUINT);
|
||||
|
||||
gtk_widget_hide(GTK_WIDGET(bar));
|
||||
}
|
||||
|
||||
void window_init(GtkNotebook *notebook)
|
||||
{
|
||||
GtkCssProvider *css = gtk_css_provider_new();
|
||||
|
||||
gtk_css_provider_load_from_path(css, "/usr/share/themes/rose/style.css", NULL);
|
||||
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(css),
|
||||
800);
|
||||
|
||||
gtk_entry_buffer_new("", 0);
|
||||
gtk_entry_set_alignment(search, 0.48);
|
||||
gtk_widget_set_size_request(GTK_WIDGET(search), 1200, -1);
|
||||
gtk_header_bar_set_custom_title(bar, GTK_WIDGET(search));
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(bar));
|
||||
|
||||
g_signal_connect(search, "activate", G_CALLBACK(search_activate), notebook);
|
||||
g_signal_connect(window, "key-press-event", G_CALLBACK(keypress), notebook);
|
||||
g_signal_connect(window, "destroy", G_CALLBACK(exit), notebook);
|
||||
}
|
||||
|
||||
void notebook_init(GtkNotebook *notebook, const char *uri)
|
||||
{
|
||||
gtk_notebook_set_show_border(notebook, false);
|
||||
gtk_notebook_set_show_tabs(notebook, false);
|
||||
notebook_append(notebook, uri);
|
||||
}
|
||||
|
||||
void setup(GtkNotebook *notebook, const char *uri)
|
||||
{
|
||||
window = GTK_WINDOW(gtk_window_new(0));
|
||||
notebook = GTK_NOTEBOOK(gtk_notebook_new());
|
||||
bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
search_buf = GTK_ENTRY_BUFFER(gtk_entry_buffer_new("", 0));
|
||||
search = GTK_ENTRY(gtk_entry_new_with_buffer(search_buf));
|
||||
|
||||
// gtk_window_fullscreen(window);
|
||||
gtk_window_set_default_size(window, WIDTH, HEIGHT);
|
||||
// gtk_window_set_resizable (window, FALSE);
|
||||
|
||||
window_init(notebook);
|
||||
notebook_init(notebook, uri);
|
||||
g_object_set(gtk_settings_get_default(), GTK, NULL);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(notebook));
|
||||
gtk_widget_show_all(GTK_WIDGET(window));
|
||||
gtk_widget_hide(GTK_WIDGET(bar));
|
||||
webkit_web_view_set_zoom_level(notebook_get_webview(notebook), ZOOM);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
GtkNotebook *notebook;
|
||||
gtk_init(NULL, NULL);
|
||||
setup(notebook, argc > 1 ? argv[1] : NULL);
|
||||
gtk_main();
|
||||
}
|
||||
150
src/config.h
|
|
@ -1,150 +0,0 @@
|
|||
#include <stdbool.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
/* 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_START_LEVEL 2.0
|
||||
#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 "file:///opt/rosenrot/flower-imgs/rose-homepage.png"
|
||||
|
||||
/* Plugins */
|
||||
#define LIBRE_REDIRECT_ENABLED true
|
||||
#define READABILITY_ENABLED true
|
||||
#define CUSTOM_USER_AGENT false
|
||||
#define ADBLOCK_ENABLED false
|
||||
#define ADBLOCK_EXTENSIONS_DIR "/opt/rosenrot/extensions"
|
||||
#define ADBLOCK_FILTERLIST_PATH "/opt/rosenrot/easylist.txt"
|
||||
/*
|
||||
To disable plugins:
|
||||
1. set their corresponding variable to false
|
||||
2. recompile
|
||||
|
||||
To remove plugins completely;
|
||||
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 */
|
||||
// https://webkitgtk.org/reference/webkit2gtk/stable/class.Settings.html
|
||||
#define WEBKIT_DEFAULT_SETTINGS \
|
||||
"enable-back-forward-navigation-gestures", true, \
|
||||
"enable-developer-extras", true, \
|
||||
"enable-smooth-scrolling", false, \
|
||||
"default-charset", "utf-8"
|
||||
#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 */
|
||||
// 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
|
||||
#define SFT 1 << 0
|
||||
#define CTRL 1 << 2
|
||||
#define ALT 1 << 3
|
||||
#define BACKTICK 96
|
||||
|
||||
/* 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,
|
||||
filter,
|
||||
|
||||
halve_window,
|
||||
rebig_window,
|
||||
|
||||
prettify,
|
||||
save_uri_to_txt,
|
||||
open_uri_in_brave,
|
||||
} func;
|
||||
|
||||
static struct {
|
||||
unsigned mod;
|
||||
unsigned key;
|
||||
func id;
|
||||
} 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 | 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, BACKTICK, prev_tab },
|
||||
{ CTRL, KEY(t), new_tab },
|
||||
{ CTRL, KEY(w), close_tab },
|
||||
|
||||
{ CTRL, KEY(l), show_searchbar },
|
||||
{ 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(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 }
|
||||
};
|
||||
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 534 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
|
@ -1 +0,0 @@
|
|||
https://www.onlygfx.com/red-rose-png-image-transparent/
|
||||
|
Before Width: | Height: | Size: 474 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 739 KiB |
|
|
@ -1,115 +0,0 @@
|
|||
# Rosenrot Adblock Plugin
|
||||
|
||||
A WebKit web extension that blocks ads using EasyList filter rules.
|
||||
|
||||
## How It Works
|
||||
|
||||
This plugin consists of two parts:
|
||||
|
||||
1. **Web Extension** (`librosenrot-adblock.so`) - A shared library loaded by WebKit into the web process. It intercepts all HTTP requests and checks them against the filter rules.
|
||||
|
||||
2. **URI Tester** (`ephy-uri-tester.c`) - The filter matching engine, adapted from Epiphany/GNOME Web browser. It parses EasyList-format filter rules and matches URLs against them.
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Download EasyList
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/rosenrot
|
||||
curl -o /tmp/easylist.txt https://easylist.to/easylist/easylist.txt
|
||||
sudo mv /tmp/easylist.txt /opt/rosenrot/easylist.txt
|
||||
```
|
||||
|
||||
### 2. Build the Extension
|
||||
|
||||
```bash
|
||||
cd src/plugins/adblock
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
This installs `librosenrot-adblock.so` to `/opt/rosenrot/extensions/`.
|
||||
|
||||
### 3. Enable in Rosenrot
|
||||
|
||||
Set `ADBLOCK_ENABLED` to `true` in `src/config.h` and rebuild rosenrot:
|
||||
|
||||
```c
|
||||
#define ADBLOCK_ENABLED true
|
||||
```
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is in `src/config.h`:
|
||||
|
||||
- `ADBLOCK_ENABLED` - Set to `true` to enable adblocking (default: `false`)
|
||||
- `ADBLOCK_FILTERLIST_PATH` - Path to easylist.txt (default: `/opt/rosenrot/easylist.txt`)
|
||||
- `ADBLOCK_EXTENSIONS_DIR` - Directory containing the extension (default: `/opt/rosenrot/extensions`)
|
||||
|
||||
## Updating Filter Lists
|
||||
|
||||
To update your filter list:
|
||||
|
||||
```bash
|
||||
curl -o /tmp/easylist.txt https://easylist.to/easylist/easylist.txt
|
||||
sudo mv /tmp/easylist.txt /opt/rosenrot/easylist.txt
|
||||
```
|
||||
|
||||
Restart rosenrot to reload the filters.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Extension not loading
|
||||
|
||||
1. Check that the extension exists:
|
||||
```bash
|
||||
ls -la /opt/rosenrot/extensions/librosenrot-adblock.so
|
||||
```
|
||||
|
||||
2. Run with debug output:
|
||||
```bash
|
||||
G_MESSAGES_DEBUG=all ./out/rosenrot
|
||||
```
|
||||
|
||||
### Filters not working
|
||||
|
||||
1. Check that easylist.txt exists and is readable:
|
||||
```bash
|
||||
head /opt/rosenrot/easylist.txt
|
||||
```
|
||||
|
||||
2. Ensure `ADBLOCK_ENABLED` is `true` in config.h
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ROSENROT PROCESS │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ WebKit Web Process │ │
|
||||
│ │ ┌──────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ librosenrot-adblock.so (extension) │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ on_send_request() ──► ephy_uri_tester_test_uri()│ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ ▼ ▼ │ │ │
|
||||
│ │ │ Return TRUE (block) Check against filters │ │ │
|
||||
│ │ │ or FALSE (allow) loaded from easylist.txt │ │ │
|
||||
│ │ └──────────────────────────────────────────────────┘ │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
- URI tester code adapted from [Epiphany/GNOME Web](https://gitlab.gnome.org/GNOME/epiphany)
|
||||
- Original wyebadblock by [jun7](https://github.com/jun7/wyebadblock)
|
||||
- Filter format by [EasyList](https://easylist.to/)
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0 (same as Epiphany source code)
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Rosenrot Adblock Web Extension
|
||||
*
|
||||
* This is a WebKit web extension that runs in the web process and
|
||||
* intercepts HTTP requests to block ads using easylist.txt filters.
|
||||
*
|
||||
* Copyright 2024 Rosenrot contributors
|
||||
* License: GPL-3.0
|
||||
*/
|
||||
|
||||
#include <webkit/webkit-web-process-extension.h>
|
||||
#include "uri_tester.h"
|
||||
|
||||
/* Configuration - must match ADBLOCK_FILTERLIST_PATH in config.h */
|
||||
#ifndef ADBLOCK_FILTERLIST_PATH
|
||||
#define ADBLOCK_FILTERLIST_PATH "/opt/rosenrot/easylist.txt"
|
||||
#endif
|
||||
|
||||
static AdblockUriTester* tester = NULL;
|
||||
static gboolean adblock_enabled = TRUE;
|
||||
|
||||
/*
|
||||
* Called for each resource request (images, scripts, stylesheets, etc.)
|
||||
* Returns TRUE to block the request, FALSE to allow it.
|
||||
*/
|
||||
static gboolean
|
||||
on_send_request(WebKitWebPage* page,
|
||||
WebKitURIRequest* request,
|
||||
WebKitURIResponse* redirect_response G_GNUC_UNUSED,
|
||||
gpointer user_data G_GNUC_UNUSED)
|
||||
{
|
||||
if (!adblock_enabled || !tester)
|
||||
return FALSE;
|
||||
|
||||
const char* request_uri = webkit_uri_request_get_uri(request);
|
||||
const char* page_uri = webkit_web_page_get_uri(page);
|
||||
|
||||
/* Don't block the main page itself */
|
||||
if (g_strcmp0(request_uri, page_uri) == 0)
|
||||
return FALSE;
|
||||
|
||||
/* Skip non-HTTP(S) requests */
|
||||
if (!g_str_has_prefix(request_uri, "http://") &&
|
||||
!g_str_has_prefix(request_uri, "https://"))
|
||||
return FALSE;
|
||||
|
||||
/* Check if this URI should be blocked */
|
||||
if (adblock_uri_tester_test_uri(tester, request_uri, page_uri)) {
|
||||
g_debug("[rosenrot-adblock] Blocked: %s", request_uri);
|
||||
return TRUE; /* Block the request */
|
||||
}
|
||||
|
||||
return FALSE; /* Allow the request */
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when a new web page is created.
|
||||
* We connect to the send-request signal to intercept all resource requests.
|
||||
*/
|
||||
static void
|
||||
on_page_created(WebKitWebProcessExtension* extension,
|
||||
WebKitWebPage* page,
|
||||
gpointer user_data)
|
||||
{
|
||||
(void)extension;
|
||||
(void)user_data;
|
||||
g_signal_connect(page, "send-request", G_CALLBACK(on_send_request), NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Extension initialization - called by WebKit when loading the extension.
|
||||
*
|
||||
* The user_data variant can contain configuration from the main browser process:
|
||||
* - "enabled": gboolean to enable/disable adblock
|
||||
*/
|
||||
G_MODULE_EXPORT void
|
||||
webkit_web_process_extension_initialize_with_user_data(WebKitWebProcessExtension* extension,
|
||||
GVariant* user_data)
|
||||
{
|
||||
/* Parse configuration from user data */
|
||||
if (user_data && g_variant_is_of_type(user_data, G_VARIANT_TYPE_VARDICT)) {
|
||||
GVariant* enabled_var = g_variant_lookup_value(user_data, "enabled", G_VARIANT_TYPE_BOOLEAN);
|
||||
if (enabled_var) {
|
||||
adblock_enabled = g_variant_get_boolean(enabled_var);
|
||||
g_variant_unref(enabled_var);
|
||||
}
|
||||
}
|
||||
|
||||
if (!adblock_enabled) {
|
||||
g_debug("[rosenrot-adblock] Adblock disabled by configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if filter file exists */
|
||||
if (!g_file_test(ADBLOCK_FILTERLIST_PATH, G_FILE_TEST_EXISTS)) {
|
||||
g_warning("[rosenrot-adblock] Filter file not found: %s", ADBLOCK_FILTERLIST_PATH);
|
||||
g_warning("[rosenrot-adblock] Download easylist.txt from https://easylist.to/easylist/easylist.txt");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the URI tester and load filters */
|
||||
tester = adblock_uri_tester_new(ADBLOCK_FILTERLIST_PATH);
|
||||
adblock_uri_tester_load(tester);
|
||||
|
||||
g_debug("[rosenrot-adblock] Loaded filters from %s", ADBLOCK_FILTERLIST_PATH);
|
||||
|
||||
/* Connect to page-created to hook into each new page */
|
||||
g_signal_connect(extension, "page-created", G_CALLBACK(on_page_created), NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallback initialization without user data
|
||||
*/
|
||||
G_MODULE_EXPORT void
|
||||
webkit_web_process_extension_initialize(WebKitWebProcessExtension* extension)
|
||||
{
|
||||
webkit_web_process_extension_initialize_with_user_data(extension, NULL);
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
# Rosenrot Adblock Extension Makefile
|
||||
#
|
||||
# This builds the adblock web extension as a shared library (.so)
|
||||
# that WebKit loads into the web process.
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -Wall -Wextra -O2 -fPIC
|
||||
LDFLAGS ?= -shared
|
||||
|
||||
# WebKit dependencies
|
||||
# Note: webkitgtk-web-process-extension-6.0 provides headers for web extensions in webkitgtk 6.0
|
||||
WEBKIT_EXT_DEPS = webkitgtk-web-process-extension-6.0
|
||||
WEBKIT_EXT_CFLAGS = $(shell pkg-config --cflags $(WEBKIT_EXT_DEPS) glib-2.0 gio-2.0 2>/dev/null || echo "-I/usr/include/webkitgtk-6.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include")
|
||||
WEBKIT_EXT_LIBS = $(shell pkg-config --libs $(WEBKIT_EXT_DEPS) glib-2.0 gio-2.0 2>/dev/null || echo "-lwebkit2gtk-6.0 -lglib-2.0 -lgio-2.0")
|
||||
|
||||
# Configuration
|
||||
ADBLOCK_FILTERLIST_PATH ?= /opt/rosenrot/easylist.txt
|
||||
EXTENSION_DIR ?= /opt/rosenrot/extensions
|
||||
|
||||
# Source files
|
||||
SRCS = adblock_extension.c uri_tester.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
TARGET = librosenrot-adblock.so
|
||||
|
||||
# Build flags
|
||||
ALL_CFLAGS = $(CFLAGS) $(WEBKIT_EXT_CFLAGS) -DADBLOCK_FILTERLIST_PATH=\"$(ADBLOCK_FILTERLIST_PATH)\"
|
||||
ALL_LDFLAGS = $(LDFLAGS) $(WEBKIT_EXT_LIBS)
|
||||
|
||||
.PHONY: all clean install uninstall
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(SRCS)
|
||||
$(CC) $(ALL_CFLAGS) $(SRCS) -o $@ $(ALL_LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET) $(OBJS)
|
||||
|
||||
install: $(TARGET)
|
||||
install -d $(DESTDIR)$(EXTENSION_DIR)
|
||||
install -m 755 $(TARGET) $(DESTDIR)$(EXTENSION_DIR)/
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(EXTENSION_DIR)/$(TARGET)
|
||||
|
||||
# Debug build
|
||||
debug: CFLAGS += -g -DDEBUG
|
||||
debug: all
|
||||
|
|
@ -1,531 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2011 Igalia S.L.
|
||||
*
|
||||
* This file is part of Epiphany.
|
||||
*
|
||||
* Epiphany is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Epiphany is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Some parts based on Midori's 'adblock' extension.
|
||||
* Adapted for rosenrot browser.
|
||||
*/
|
||||
|
||||
#include "uri_tester.h"
|
||||
#include <string.h>
|
||||
|
||||
#define SIGNATURE_SIZE 8
|
||||
|
||||
struct _AdblockUriTester {
|
||||
GObject parent_instance;
|
||||
|
||||
char* filter_file_path;
|
||||
|
||||
GHashTable* pattern;
|
||||
GHashTable* keys;
|
||||
GHashTable* optslist;
|
||||
GHashTable* urlcache;
|
||||
|
||||
GHashTable* whitelisted_pattern;
|
||||
GHashTable* whitelisted_keys;
|
||||
GHashTable* whitelisted_optslist;
|
||||
GHashTable* whitelisted_urlcache;
|
||||
|
||||
GRegex* regex_third_party;
|
||||
GRegex* regex_pattern;
|
||||
GRegex* regex_subdocument;
|
||||
|
||||
gboolean loaded;
|
||||
};
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_FILTER_FILE_PATH,
|
||||
LAST_PROP
|
||||
};
|
||||
|
||||
static GParamSpec* obj_properties[LAST_PROP];
|
||||
|
||||
G_DEFINE_TYPE(AdblockUriTester, adblock_uri_tester, G_TYPE_OBJECT)
|
||||
|
||||
static GString* fixup_regexp(const char* prefix, char* src);
|
||||
|
||||
static inline gboolean
|
||||
check_rule(AdblockUriTester* tester,
|
||||
GRegex* regex,
|
||||
const char* patt,
|
||||
const char* req_uri,
|
||||
const char* page_uri,
|
||||
gboolean whitelist)
|
||||
{
|
||||
char* opts;
|
||||
GHashTable* optslist = whitelist ? tester->whitelisted_optslist : tester->optslist;
|
||||
|
||||
if (!g_regex_match_full(regex, req_uri, -1, 0, 0, NULL, NULL))
|
||||
return FALSE;
|
||||
|
||||
opts = g_hash_table_lookup(optslist, patt);
|
||||
if (opts && g_regex_match(tester->regex_third_party, opts, 0, NULL)) {
|
||||
if (page_uri && g_regex_match_full(regex, page_uri, -1, 0, 0, NULL, NULL))
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
is_matched_by_pattern(AdblockUriTester* tester,
|
||||
const char* req_uri,
|
||||
const char* page_uri,
|
||||
gboolean whitelist)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer patt, regex;
|
||||
GHashTable* pattern = whitelist ? tester->whitelisted_pattern : tester->pattern;
|
||||
|
||||
g_hash_table_iter_init(&iter, pattern);
|
||||
while (g_hash_table_iter_next(&iter, &patt, ®ex)) {
|
||||
if (check_rule(tester, regex, patt, req_uri, page_uri, whitelist))
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
is_matched_by_key(AdblockUriTester* tester,
|
||||
const char* req_uri,
|
||||
const char* page_uri,
|
||||
gboolean whitelist)
|
||||
{
|
||||
int len;
|
||||
int pos = 0;
|
||||
GList* regex_bl = NULL;
|
||||
GString* guri;
|
||||
gboolean ret = FALSE;
|
||||
char sig[SIGNATURE_SIZE + 1];
|
||||
GHashTable* keys = whitelist ? tester->whitelisted_keys : tester->keys;
|
||||
|
||||
memset(&sig[0], 0, sizeof(sig));
|
||||
guri = fixup_regexp("", (char*)req_uri);
|
||||
if (!guri)
|
||||
return FALSE;
|
||||
len = guri->len;
|
||||
|
||||
for (pos = len - SIGNATURE_SIZE; pos >= 0; pos--) {
|
||||
GRegex* regex;
|
||||
strncpy(sig, guri->str + pos, SIGNATURE_SIZE);
|
||||
regex = g_hash_table_lookup(keys, sig);
|
||||
|
||||
if (!regex || g_list_find(regex_bl, regex))
|
||||
continue;
|
||||
ret = check_rule(tester, regex, sig, req_uri, page_uri, whitelist);
|
||||
if (ret)
|
||||
break;
|
||||
regex_bl = g_list_prepend(regex_bl, regex);
|
||||
}
|
||||
g_string_free(guri, TRUE);
|
||||
g_list_free(regex_bl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
is_matched(AdblockUriTester* tester,
|
||||
const char* req_uri,
|
||||
const char* page_uri,
|
||||
gboolean whitelist)
|
||||
{
|
||||
gpointer is_matched_val;
|
||||
GHashTable* urlcache = whitelist ? tester->whitelisted_urlcache : tester->urlcache;
|
||||
|
||||
if (g_hash_table_lookup_extended(urlcache, req_uri, NULL, &is_matched_val))
|
||||
return GPOINTER_TO_INT(is_matched_val);
|
||||
|
||||
if (is_matched_by_key(tester, req_uri, page_uri, whitelist)) {
|
||||
g_hash_table_insert(urlcache, g_strdup(req_uri), GINT_TO_POINTER(TRUE));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (is_matched_by_pattern(tester, req_uri, page_uri, whitelist)) {
|
||||
g_hash_table_insert(urlcache, g_strdup(req_uri), GINT_TO_POINTER(TRUE));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_hash_table_insert(urlcache, g_strdup(req_uri), GINT_TO_POINTER(FALSE));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GString*
|
||||
fixup_regexp(const char* prefix, char* src)
|
||||
{
|
||||
GString* str;
|
||||
|
||||
if (!src)
|
||||
return NULL;
|
||||
|
||||
str = g_string_new(prefix);
|
||||
|
||||
if (src[0] == '*') {
|
||||
src++;
|
||||
}
|
||||
|
||||
do {
|
||||
switch (*src) {
|
||||
case '*':
|
||||
g_string_append(str, ".*");
|
||||
break;
|
||||
case '^':
|
||||
g_string_append(str, "([^a-zA-Z\\d]|[_\\-\\.%])");
|
||||
break;
|
||||
case '|':
|
||||
if (src[1] == '\0')
|
||||
g_string_append(str, "$");
|
||||
else
|
||||
g_string_append(str, "\\|");
|
||||
break;
|
||||
case '.':
|
||||
case '+':
|
||||
case '?':
|
||||
case '[':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
case '(':
|
||||
case ')':
|
||||
case '\\':
|
||||
g_string_append_printf(str, "\\%c", *src);
|
||||
break;
|
||||
default:
|
||||
g_string_append_printf(str, "%c", *src);
|
||||
break;
|
||||
}
|
||||
src++;
|
||||
} while (*src);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static void
|
||||
compile_regexp(AdblockUriTester* tester,
|
||||
GString* gpatt,
|
||||
const char* opts,
|
||||
gboolean whitelist)
|
||||
{
|
||||
GHashTable* pattern;
|
||||
GHashTable* keys;
|
||||
GHashTable* optslist;
|
||||
GRegex* regex;
|
||||
GError* error = NULL;
|
||||
char* patt;
|
||||
int len;
|
||||
|
||||
if (!gpatt)
|
||||
return;
|
||||
|
||||
patt = gpatt->str;
|
||||
len = gpatt->len;
|
||||
|
||||
regex = g_regex_new(patt, G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY, &error);
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
pattern = whitelist ? tester->whitelisted_pattern : tester->pattern;
|
||||
keys = whitelist ? tester->whitelisted_keys : tester->keys;
|
||||
optslist = whitelist ? tester->whitelisted_optslist : tester->optslist;
|
||||
|
||||
if (!g_regex_match(tester->regex_pattern, patt, 0, NULL)) {
|
||||
int signature_count = 0;
|
||||
int pos = 0;
|
||||
char* sig;
|
||||
|
||||
for (pos = len - SIGNATURE_SIZE; pos >= 0; pos--) {
|
||||
sig = g_strndup(patt + pos, SIGNATURE_SIZE);
|
||||
if (!strchr(sig, '*') && !g_hash_table_lookup(keys, sig)) {
|
||||
g_hash_table_insert(keys, g_strdup(sig), g_regex_ref(regex));
|
||||
g_hash_table_insert(optslist, g_strdup(sig), g_strdup(opts));
|
||||
signature_count++;
|
||||
} else {
|
||||
if (sig[0] == '*' && !g_hash_table_lookup(pattern, patt)) {
|
||||
g_hash_table_insert(pattern, g_strdup(patt), g_regex_ref(regex));
|
||||
g_hash_table_insert(optslist, g_strdup(patt), g_strdup(opts));
|
||||
}
|
||||
}
|
||||
g_free(sig);
|
||||
}
|
||||
g_regex_unref(regex);
|
||||
|
||||
if (signature_count > 1 && g_hash_table_lookup(pattern, patt))
|
||||
g_hash_table_remove(pattern, patt);
|
||||
} else {
|
||||
g_hash_table_insert(pattern, g_strdup(patt), regex);
|
||||
g_hash_table_insert(optslist, g_strdup(patt), g_strdup(opts));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_url_pattern(AdblockUriTester* tester,
|
||||
const char* prefix,
|
||||
const char* type,
|
||||
char* line,
|
||||
gboolean whitelist)
|
||||
{
|
||||
char** data;
|
||||
char* patt;
|
||||
GString* format_patt;
|
||||
const char* opts;
|
||||
|
||||
data = g_strsplit(line, "$", -1);
|
||||
if (!data || !data[0]) {
|
||||
g_strfreev(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data[1] && data[2]) {
|
||||
patt = g_strconcat(data[0], data[1], NULL);
|
||||
opts = g_strconcat(type, ",", data[2], NULL);
|
||||
} else if (data[1]) {
|
||||
patt = data[0];
|
||||
opts = g_strconcat(type, ",", data[1], NULL);
|
||||
} else {
|
||||
patt = data[0];
|
||||
opts = type;
|
||||
}
|
||||
|
||||
if (g_regex_match(tester->regex_subdocument, opts, 0, NULL)) {
|
||||
if (data[1] && data[2])
|
||||
g_free(patt);
|
||||
if (data[1])
|
||||
g_free((char*)opts);
|
||||
g_strfreev(data);
|
||||
return;
|
||||
}
|
||||
|
||||
format_patt = fixup_regexp(prefix, patt);
|
||||
|
||||
compile_regexp(tester, format_patt, opts, whitelist);
|
||||
|
||||
if (data[1] && data[2])
|
||||
g_free(patt);
|
||||
if (data[1])
|
||||
g_free((char*)opts);
|
||||
g_strfreev(data);
|
||||
|
||||
if (format_patt)
|
||||
g_string_free(format_patt, TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
parse_line(AdblockUriTester* tester,
|
||||
char* line,
|
||||
gboolean whitelist)
|
||||
{
|
||||
if (!line)
|
||||
return;
|
||||
|
||||
g_strchomp(line);
|
||||
|
||||
if (line[0] == '!')
|
||||
return;
|
||||
if (line[0] == '[')
|
||||
return;
|
||||
|
||||
if (g_str_has_prefix(line, "@@")) {
|
||||
parse_line(tester, line + 2, TRUE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strstr(line, "domain="))
|
||||
return;
|
||||
|
||||
if (line[0] == ' ' || !line[0])
|
||||
return;
|
||||
|
||||
if (line[0] == '#')
|
||||
return;
|
||||
|
||||
if (strstr(line, "##"))
|
||||
return;
|
||||
|
||||
if (strchr(line, '#'))
|
||||
return;
|
||||
|
||||
if (line[0] == '|' && line[1] == '|') {
|
||||
line += 2;
|
||||
add_url_pattern(tester, "^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?", "fulluri", line, whitelist);
|
||||
return;
|
||||
}
|
||||
if (line[0] == '|') {
|
||||
line++;
|
||||
add_url_pattern(tester, "^", "fulluri", line, whitelist);
|
||||
return;
|
||||
}
|
||||
add_url_pattern(tester, "", "uri", line, whitelist);
|
||||
}
|
||||
|
||||
static void
|
||||
load_filters_sync(AdblockUriTester* tester)
|
||||
{
|
||||
GFile* file;
|
||||
GFileInputStream* stream;
|
||||
GDataInputStream* data_stream;
|
||||
GError* error = NULL;
|
||||
char* line;
|
||||
|
||||
if (!tester->filter_file_path)
|
||||
return;
|
||||
|
||||
file = g_file_new_for_path(tester->filter_file_path);
|
||||
if (!g_file_query_exists(file, NULL)) {
|
||||
g_object_unref(file);
|
||||
return;
|
||||
}
|
||||
|
||||
stream = g_file_read(file, NULL, &error);
|
||||
g_object_unref(file);
|
||||
|
||||
if (!stream) {
|
||||
if (error) {
|
||||
g_warning("Failed to open filter file: %s", error->message);
|
||||
g_error_free(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
data_stream = g_data_input_stream_new(G_INPUT_STREAM(stream));
|
||||
g_object_unref(stream);
|
||||
|
||||
while ((line = g_data_input_stream_read_line(data_stream, NULL, NULL, NULL)) != NULL) {
|
||||
parse_line(tester, line, FALSE);
|
||||
g_free(line);
|
||||
}
|
||||
|
||||
g_object_unref(data_stream);
|
||||
tester->loaded = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
adblock_uri_tester_init(AdblockUriTester* tester)
|
||||
{
|
||||
tester->pattern = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, (GDestroyNotify)g_regex_unref);
|
||||
tester->keys = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, (GDestroyNotify)g_regex_unref);
|
||||
tester->optslist = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, (GDestroyNotify)g_free);
|
||||
tester->urlcache = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, NULL);
|
||||
|
||||
tester->whitelisted_pattern = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, (GDestroyNotify)g_regex_unref);
|
||||
tester->whitelisted_keys = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, (GDestroyNotify)g_regex_unref);
|
||||
tester->whitelisted_optslist = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, (GDestroyNotify)g_free);
|
||||
tester->whitelisted_urlcache = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, NULL);
|
||||
|
||||
tester->regex_third_party = g_regex_new(",third-party",
|
||||
G_REGEX_CASELESS | G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY, NULL);
|
||||
tester->regex_pattern = g_regex_new("^/.*[\\^\\$\\*].*/$",
|
||||
G_REGEX_UNGREEDY | G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY, NULL);
|
||||
tester->regex_subdocument = g_regex_new("subdocument",
|
||||
G_REGEX_CASELESS | G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY, NULL);
|
||||
|
||||
tester->loaded = FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
adblock_uri_tester_set_property(GObject* object,
|
||||
guint prop_id,
|
||||
const GValue* value,
|
||||
GParamSpec* pspec)
|
||||
{
|
||||
AdblockUriTester* tester = ADBLOCK_URI_TESTER(object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FILTER_FILE_PATH:
|
||||
tester->filter_file_path = g_value_dup_string(value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
adblock_uri_tester_finalize(GObject* object)
|
||||
{
|
||||
AdblockUriTester* tester = ADBLOCK_URI_TESTER(object);
|
||||
|
||||
g_free(tester->filter_file_path);
|
||||
|
||||
g_hash_table_destroy(tester->pattern);
|
||||
g_hash_table_destroy(tester->keys);
|
||||
g_hash_table_destroy(tester->optslist);
|
||||
g_hash_table_destroy(tester->urlcache);
|
||||
|
||||
g_hash_table_destroy(tester->whitelisted_pattern);
|
||||
g_hash_table_destroy(tester->whitelisted_keys);
|
||||
g_hash_table_destroy(tester->whitelisted_optslist);
|
||||
g_hash_table_destroy(tester->whitelisted_urlcache);
|
||||
|
||||
g_regex_unref(tester->regex_third_party);
|
||||
g_regex_unref(tester->regex_pattern);
|
||||
g_regex_unref(tester->regex_subdocument);
|
||||
|
||||
G_OBJECT_CLASS(adblock_uri_tester_parent_class)->finalize(object);
|
||||
}
|
||||
|
||||
static void
|
||||
adblock_uri_tester_class_init(AdblockUriTesterClass* klass)
|
||||
{
|
||||
GObjectClass* object_class = G_OBJECT_CLASS(klass);
|
||||
|
||||
object_class->set_property = adblock_uri_tester_set_property;
|
||||
object_class->finalize = adblock_uri_tester_finalize;
|
||||
|
||||
obj_properties[PROP_FILTER_FILE_PATH] = g_param_spec_string("filter-file-path",
|
||||
"Filter file path",
|
||||
"Path to the easylist.txt filter file",
|
||||
NULL,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties(object_class, LAST_PROP, obj_properties);
|
||||
}
|
||||
|
||||
AdblockUriTester*
|
||||
adblock_uri_tester_new(const char* filter_file_path)
|
||||
{
|
||||
return ADBLOCK_URI_TESTER(g_object_new(ADBLOCK_TYPE_URI_TESTER,
|
||||
"filter-file-path", filter_file_path, NULL));
|
||||
}
|
||||
|
||||
void adblock_uri_tester_load(AdblockUriTester* tester)
|
||||
{
|
||||
if (tester->loaded)
|
||||
return;
|
||||
load_filters_sync(tester);
|
||||
}
|
||||
|
||||
gboolean
|
||||
adblock_uri_tester_test_uri(AdblockUriTester* tester,
|
||||
const char* request_uri,
|
||||
const char* page_uri)
|
||||
{
|
||||
if (!tester->loaded)
|
||||
return FALSE;
|
||||
|
||||
if (is_matched(tester, request_uri, page_uri, TRUE))
|
||||
return FALSE;
|
||||
|
||||
return is_matched(tester, request_uri, page_uri, FALSE);
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2011 Igalia S.L.
|
||||
*
|
||||
* This file is part of Epiphany.
|
||||
*
|
||||
* Epiphany is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Epiphany is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Adapted for rosenrot browser.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define ADBLOCK_TYPE_URI_TESTER (adblock_uri_tester_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE(AdblockUriTester, adblock_uri_tester, ADBLOCK, URI_TESTER, GObject)
|
||||
|
||||
AdblockUriTester* adblock_uri_tester_new(const char* filter_file_path);
|
||||
void adblock_uri_tester_load(AdblockUriTester* tester);
|
||||
gboolean adblock_uri_tester_test_uri(AdblockUriTester* tester,
|
||||
const char* request_uri,
|
||||
const char* page_uri);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
## About
|
||||
|
||||
This code automatically redirects webpage to their open-source frontends. It is based on <https://libredirect.codeberg.page/>
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../strings/strings.h"
|
||||
|
||||
#define LIBRE_N 50
|
||||
|
||||
/* Inspired by https://libredirect.github.io/, but in C. */
|
||||
int libre_redirect(const char* uri, char* output)
|
||||
{
|
||||
int len_uri = strlen(uri);
|
||||
int len_output = strlen(output);
|
||||
|
||||
if ((len_output - len_uri) < LIBRE_N) {
|
||||
fprintf(stderr, "Not enough memory\n");
|
||||
return 1; // not enough memory.
|
||||
} else {
|
||||
char* annoying_sites[] = {
|
||||
"https://www.reddit.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://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.ph/https://www.bloomberg.com",
|
||||
"https://royalread.nunosempere.com",
|
||||
"https://dumb.vern.cc",
|
||||
"https://example.com"
|
||||
// "https://wayback.nunosempere.com",
|
||||
// "https://nitter.net"
|
||||
};
|
||||
|
||||
int len = sizeof(annoying_sites) / sizeof(annoying_sites[0]);
|
||||
for (int i = 0; i < len; i++) {
|
||||
str_init(output, len_output);
|
||||
int replace_check = str_replace_start(uri, annoying_sites[i],
|
||||
alternatives[i], output);
|
||||
switch (replace_check) {
|
||||
case 0: // no match found
|
||||
break;
|
||||
case 1: // str_replace_start somehow failed
|
||||
fprintf(stderr, "str_replace_start failed\n");
|
||||
return 1;
|
||||
break;
|
||||
case 2: // match succeeded
|
||||
return 2;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unreachable state\n");
|
||||
}
|
||||
}
|
||||
// Use strncpy with explicit null termination for safety
|
||||
strncpy(output, uri, len_output - 1);
|
||||
output[len_output - 1] = '\0';
|
||||
}
|
||||
|
||||
int utm_check = str_destructively_omit_from(output, "?utm");
|
||||
return utm_check * 2;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#define LIBRE_N 50
|
||||
|
||||
int libre_redirect(const char* uri, char* uri_filtered);
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
#include "../libre_redirect.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
char uri[] = "https://reddit.com/r/blah";
|
||||
|
||||
int l = LIBRE_N + strlen(uri) + 1;
|
||||
char uri_filtered[l];
|
||||
str_init(uri_filtered, l);
|
||||
|
||||
if (!libre_redirect(uri, uri_filtered)) {
|
||||
printf("Filtered uri: %s\n", uri_filtered);
|
||||
} else {
|
||||
printf("Uri: %s\n", uri);
|
||||
// failure; do something with the original uri.
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#include "strings/strings.h"
|
||||
#include "libre_redirect/libre_redirect.h"
|
||||
#include "readability/readability.h"
|
||||
#include "shortcuts/shortcuts.h"
|
||||
#include "style/style.h"
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
## Shared
|
||||
COMMON_CODE=./src/plugins/strings/strings.c
|
||||
|
||||
## Plugins
|
||||
CUSTOM_STYLES=./src/plugins/style/style.c
|
||||
SHORTCUTS=./src/plugins/shortcuts/shortcuts.c
|
||||
READABILITY=./src/plugins/readability/readability.c
|
||||
LIBRE_REDIRECT=./src/plugins/libre_redirect/libre_redirect.c
|
||||
|
||||
STAND_IN=./src/plugins/stand_in/stand_in.c # gives function definitions for the above, which do nothing
|
||||
|
||||
PLUGINS=$(COMMON_CODE) $(CUSTOM_STYLES) $(SHORTCUTS) $(READABILITY) $(LIBRE_REDIRECT)
|
||||
# PLUGINS=$(STAND_IN)
|
||||
|
||||
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
This code reimplements firefox readability mode. Code taken from <https://raw.githubusercontent.com/ushnisha/readability-reader-webextensions/master/content_scripts/tranquilize.js>
|
||||
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
#include <glib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define READABILITY_N 88212 + 1000
|
||||
|
||||
void read_readability_js(char* string)
|
||||
{
|
||||
gchar* file_contents = NULL;
|
||||
gsize length = 0;
|
||||
GError* error = NULL;
|
||||
|
||||
if (!g_file_get_contents("/opt/rosenrot/readability.js", &file_contents, &length, &error)) {
|
||||
fprintf(stderr, "Failed to open file: %s\n", error ? error->message : "unknown error");
|
||||
fprintf(stderr, "Consider running $ sudo make runtime_files\n");
|
||||
if (error) g_error_free(error);
|
||||
string[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if (length > READABILITY_N) {
|
||||
fprintf(stderr, "readability.js file is too large (%zu bytes, max %d)\n", length, READABILITY_N);
|
||||
fprintf(stderr, "Consider increasing READABILITY_N or running recompute_READABILITY_N.sh\n");
|
||||
g_free(file_contents);
|
||||
string[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(string, file_contents, length);
|
||||
string[length] = '\0';
|
||||
g_free(file_contents);
|
||||
}
|
||||
|
||||
/*
|
||||
int main(){
|
||||
char* readability_js = malloc(READABILITY_N+1);
|
||||
read_readability_js(readability_js);
|
||||
printf("%s", readability_js);
|
||||
free(readability_js);
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#define READABILITY_N 88212 + 1000
|
||||
|
||||
void read_readability_js(char* string);
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#!/bin/sh
|
||||
sed_wrapper()
|
||||
{
|
||||
find ./ -type f -exec sed -i -e "$1" {} \;
|
||||
} ## e.g., sedr "s/target/replacement/g"
|
||||
|
||||
READABILITY_N=`wc -c readability.js | cut -d " " -f 1`
|
||||
sed_wrapper "s/^#define READABILITY_N .*/#define READABILITY_N $READABILITY_N + 1000/g"
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
## About
|
||||
|
||||
This code automatically redirects shortcuts to their longer expansions. Similar to DuckDuckGo's bangs (<https://duckduckgo.com/bangs>)
|
||||
|
||||
Note that Whoogle (the default search engine) also has its own bangs!
|
||||
- See: https://github.com/benbusby/whoogle-search/blob/main/app/utils/bangs.py
|
||||
- and https://duckduckgo.com/bang.v255.js
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../strings/strings.h"
|
||||
|
||||
#define SHORTCUT_N 41
|
||||
#define DEBUG false
|
||||
|
||||
/* Inspired by https://duckduckgo.com/bangs */
|
||||
int shortcut_expand(const char* uri, char* output)
|
||||
{
|
||||
int len_uri = strlen(uri);
|
||||
int len_output = strlen(output);
|
||||
|
||||
if ((len_output - len_uri) < SHORTCUT_N) {
|
||||
fprintf(stderr, "Not enough memory\n");
|
||||
return 1; // not enough memory.
|
||||
} else {
|
||||
char* shortcuts[] = {
|
||||
"!aa",
|
||||
"!blog",
|
||||
"!fnf",
|
||||
"!fnc",
|
||||
"!fs",
|
||||
"!hn",
|
||||
"!hnb",
|
||||
"!ww",
|
||||
"!x"
|
||||
};
|
||||
|
||||
char* expansions[] = {
|
||||
"https://annas-archive.org",
|
||||
"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://web.whatsapp.com",
|
||||
"https://twitter.com"
|
||||
};
|
||||
|
||||
// len = sizeof(shortcuts) / sizeof(shortcuts[0]);
|
||||
int len = sizeof(shortcuts) / sizeof(char*);
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
str_init(output, len_output);
|
||||
int replace_check = str_replace_start(uri, shortcuts[i],
|
||||
expansions[i], output);
|
||||
switch (replace_check) {
|
||||
case 0: // no match found
|
||||
break;
|
||||
case 1: // str_replace_start somehow failed
|
||||
fprintf(stderr, "str_replace_start failed\n");
|
||||
return 1;
|
||||
break;
|
||||
case 2: // match succeeded
|
||||
return 2;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unreachable state\n");
|
||||
}
|
||||
}
|
||||
// Use strncpy with explicit null termination for safety
|
||||
strncpy(output, uri, len_output - 1);
|
||||
output[len_output - 1] = '\0';
|
||||
}
|
||||
if (DEBUG) printf("No match found\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#define SHORTCUT_N 41
|
||||
|
||||
int shortcut_expand(const char* uri, char* output);
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
/* Why this file is needed:
|
||||
* If the plugins are disabled,
|
||||
* their conditionals will never resolve
|
||||
* and their functionality never comes into play
|
||||
* but the compiler still wants to know their type
|
||||
*/
|
||||
|
||||
int libre_redirect(const char* uri, char* uri_filtered){
|
||||
return 0;
|
||||
}
|
||||
void str_init(char* str, int n){
|
||||
|
||||
};
|
||||
|
||||
int str_replace_start(const char* string, const char* target,
|
||||
const char* replacement, char* output){
|
||||
return 1;
|
||||
};
|
||||
|
||||
void read_readability_js(char* string){
|
||||
|
||||
}
|
||||
|
||||
void read_style_js(char* string){
|
||||
|
||||
}
|
||||
|
||||
int shortcut_expand(const char* uri, char* output){
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/* Why this file is needed:
|
||||
* If the plugins are disabled,
|
||||
* their conditionals will never resolve
|
||||
* and their functionality never comes into play
|
||||
* but the compiler still wants to know their type
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define LIBRE_N 0
|
||||
#define STYLE_N 0
|
||||
#define READABILITY_N 84638 + 1
|
||||
|
||||
int libre_redirect(const char* uri, char* uri_filtered);
|
||||
void str_init(char* str, int n);
|
||||
|
||||
int str_replace_start(const char* string, const char* target,
|
||||
const char* replacement, char* output);
|
||||
|
||||
void read_readability_js(char* string);
|
||||
|
||||
void read_style_js(char* string);
|
||||
|
||||
int shortcut_expand(const char* uri, char* output);
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DEBUG false
|
||||
|
||||
// String manipulation
|
||||
void str_init(char* str, int n)
|
||||
{
|
||||
// could also use <https://manpages.ubuntu.com/manpages/impish/man3/strinit.3pub.html>
|
||||
for (int i = 0; i < n; i++)
|
||||
str[i] = ' ';
|
||||
str[n-1] = '\0';
|
||||
}
|
||||
int str_replace_start(const char* string, const char* target, const char* replacement, char* output)
|
||||
{
|
||||
int l1 = strlen(string);
|
||||
int l2 = strlen(target);
|
||||
int l3 = strlen(replacement);
|
||||
int l4 = strlen(output);
|
||||
|
||||
if (DEBUG) {
|
||||
printf("string: %s, target: %s, replacement: %s, output: %s\n", string, target, replacement, output);
|
||||
printf("%d,%d,%d,%d\n", l1, l2, l3, l4);
|
||||
}
|
||||
|
||||
if ((l4 < (l1 - l2 + l3)) || l4 < l1) {
|
||||
printf("Not enough memory in output string.\n");
|
||||
return 1;
|
||||
}
|
||||
int match = true;
|
||||
for (int i = 0; i < l2; i++) {
|
||||
if (string[i] != target[i]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
if (DEBUG) printf("Found match.\n");
|
||||
for (int i = 0; i < l3; i++) {
|
||||
output[i] = replacement[i];
|
||||
}
|
||||
int counter = l3;
|
||||
for (int i = l2; i < l1; i++) {
|
||||
output[counter] = string[i];
|
||||
counter++;
|
||||
}
|
||||
output[counter] = '\0';
|
||||
return 2; // success
|
||||
} else {
|
||||
if (DEBUG) printf("Did not find match.\n");
|
||||
// Use strncpy with explicit null termination for safety
|
||||
strncpy(output, string, l4 - 1);
|
||||
output[l4 - 1] = '\0';
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
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(char* input, const char* from);
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
## Customize css style for individual websites.
|
||||
|
||||
- Replicates: <https://addons.mozilla.org/en-GB/firefox/addon/styl-us/>.
|
||||
- The template is similar to the readability folder.
|
||||
- You will also want to customize the `style.c` file.
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#!/bin/sh
|
||||
sed_wrapper()
|
||||
{
|
||||
find ./ -type f -exec sed -i -e "$1" {} \;
|
||||
} ## e.g., sedr "s/target/replacement/g"
|
||||
|
||||
STYLE_N=`wc -c style.js | cut -d " " -f 1`
|
||||
sed_wrapper "s/^#define STYLE_N .*/#define STYLE_N $STYLE_N + 1000/g"
|
||||
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#include <glib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define STYLE_N 9404 + 1000
|
||||
|
||||
void read_style_js(char* string)
|
||||
{
|
||||
gchar* file_contents = NULL;
|
||||
gsize length = 0;
|
||||
GError* error = NULL;
|
||||
|
||||
if (!g_file_get_contents("/opt/rosenrot/style.js", &file_contents, &length, &error)) {
|
||||
fprintf(stderr, "Failed to open file: %s\n", error ? error->message : "unknown error");
|
||||
if (error) g_error_free(error);
|
||||
string[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if (length > STYLE_N) {
|
||||
fprintf(stderr, "style.js file is too large (%zu bytes, max %d)\n", length, STYLE_N);
|
||||
fprintf(stderr, "Consider increasing STYLE_N or running recompute_STYLE_N.sh\n");
|
||||
g_free(file_contents);
|
||||
string[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(string, file_contents, length);
|
||||
string[length] = '\0';
|
||||
g_free(file_contents);
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#define STYLE_N 9404 + 1000
|
||||
|
||||
void read_style_js(char* string);
|
||||
|
|
@ -1,359 +0,0 @@
|
|||
// 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 = `
|
||||
/*
|
||||
.Layout-main {
|
||||
margin-left: 100px;
|
||||
}
|
||||
.SingleColumnSection-root {
|
||||
width: 1000px !important;
|
||||
max-width: 1400px !important;
|
||||
padding-left: 100px !important;
|
||||
}
|
||||
.NavigationStandalone-sidebar {
|
||||
display: none;
|
||||
}
|
||||
.intercom-lightweight-app{
|
||||
display: none;
|
||||
}
|
||||
*/
|
||||
`;
|
||||
break;
|
||||
case "nationstates.net":
|
||||
styles = `
|
||||
.adidentifier {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
break;
|
||||
case "mail.proton.me":
|
||||
styles = `
|
||||
/*
|
||||
.item-container-row.read, .item-container.read {
|
||||
background-color: white;
|
||||
}
|
||||
.item-container-row.unread, .item-container.unread {
|
||||
background-color: #E8E8E8;
|
||||
}
|
||||
.selection .item-container-row.item-is-selected, .item-container.item-is-selected {
|
||||
background-color: var(--selection-background-color) !important;
|
||||
}
|
||||
zoom: 0.625 !important;
|
||||
*/
|
||||
`;
|
||||
break;
|
||||
case "forum.nunosempere.com":
|
||||
styles = `
|
||||
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":
|
||||
styles = `
|
||||
/*
|
||||
body {
|
||||
zoom: 1.8;
|
||||
}
|
||||
*/
|
||||
|
||||
footer {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
break;
|
||||
case "reddit.com":
|
||||
// fallthrough
|
||||
case "old.reddit.com":
|
||||
styles = `
|
||||
/* kill sidebar ads */
|
||||
.ad-container,
|
||||
a[href^="https://alb.reddit.com"]
|
||||
a[href="/premium"],
|
||||
[data-promoted^="true"],
|
||||
#eu-cookie-policy,
|
||||
.infobar-toaster-container,
|
||||
.listingsignupbar,
|
||||
.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"])
|
||||
[data-testid="cellInnerDiv"]:has(svg + [dir="auto"]) {
|
||||
display: none;
|
||||
}
|
||||
[data-testid^="placementTracking"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* hide what's happening section */
|
||||
:has(meta[property="og:site_name"][content="Twitter"])
|
||||
[aria-label="Timeline: Trending now"] {
|
||||
display: none !important;
|
||||
}
|
||||
[data-testid^="sidebarColumn"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide DMs v2 */
|
||||
[data-testid^="DMDrawerHeader"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tweak main column */
|
||||
[data-testid^="primaryColumn"] {
|
||||
min-width: 900px;
|
||||
max-width: 900px;
|
||||
}
|
||||
[data-testid^="cellInnerDiv"] {
|
||||
min-width: 700px;
|
||||
max-width: 700px;
|
||||
}
|
||||
[aria-label^="Timeline: Conversation"]{
|
||||
margin-left: 145px;
|
||||
}
|
||||
[data-testid^="DMDrawer"]{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Delete a few unused or annoying elements */
|
||||
[aria-label^="Verified Orgs"] {
|
||||
display: none;
|
||||
}
|
||||
[aria-label^="Lists"] {
|
||||
display: none;
|
||||
}
|
||||
[aria-label^="Communities"] {
|
||||
display: none;
|
||||
}
|
||||
[aria-label^="Primary"] {
|
||||
margin-top: 50px;
|
||||
}
|
||||
[role^="progressbar"]{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* hide video */
|
||||
[data-testid^="videoPlayer"] {
|
||||
display: 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;
|
||||
}
|
||||
|
||||
/* No transparency at the top */
|
||||
[aria-live^="polite"]{
|
||||
background: white !important;
|
||||
}
|
||||
`;
|
||||
break;
|
||||
case "":
|
||||
break;
|
||||
default:
|
||||
console.log(`Domain: ${document.domain}`);
|
||||
console.log("No custom style");
|
||||
}
|
||||
|
||||
if (styles != null) {
|
||||
var styleSheet = document.createElement("style");
|
||||
styleSheet.innerText = styles;
|
||||
document.head.appendChild(styleSheet);
|
||||
console.log("Style changed");
|
||||
}
|
||||
|
||||
// Extra: Replace default alert with new function
|
||||
// whose style can be changed!
|
||||
window.alert = (message) => {
|
||||
let alertDiv = document.getElementById("customAlert");
|
||||
if (!alertDiv) {
|
||||
const html = `
|
||||
<div id="customAlert" class="custom-alert">
|
||||
<div class="custom-alert-content">
|
||||
<p id="alertMessage"></p>
|
||||
<button id="alertOkButton">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.custom-alert {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
.custom-alert-content {
|
||||
background-color: #fefefe;
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 80%;
|
||||
font-family: monospace; /* Use monospace font */
|
||||
}
|
||||
.visible {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
document.body.insertAdjacentHTML("beforeend", html);
|
||||
alertDiv = document.getElementById("customAlert");
|
||||
document.getElementById("alertOkButton").onclick = () => {
|
||||
alertDiv.classList.remove("visible");
|
||||
document.removeEventListener("keydown", dismissAlert);
|
||||
};
|
||||
}
|
||||
|
||||
const dismissAlert = (event) => {
|
||||
if (
|
||||
event.key === "Enter" /*&& event.ctrlKey*/ &&
|
||||
alertDiv.classList.contains("visible")
|
||||
) {
|
||||
alertDiv.classList.remove("visible");
|
||||
document.removeEventListener("keydown", dismissAlert);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", dismissAlert);
|
||||
document.getElementById("alertMessage").textContent = message;
|
||||
alertDiv.classList.add("visible");
|
||||
};
|
||||
|
||||
// Extra: hide video players on twitter
|
||||
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
|
||||
|
||||
function hideVideoPlayerGrandparent() {
|
||||
document
|
||||
.querySelectorAll('[data-testid="videoPlayer"]')
|
||||
.forEach(function (videoPlayer) {
|
||||
var grandparentElement =
|
||||
videoPlayer.parentElement.parentElement.parentElement.parentElement
|
||||
.parentElement.parentElement;
|
||||
var newTextElement = document.createElement("div");
|
||||
newTextElement.textContent = " [ twitter video ] ";
|
||||
newTextElement.style["margin-top"] = "10px";
|
||||
newTextElement.style["margin-left"] = "10px";
|
||||
newTextElement.style["margin-bottom"] = "10px";
|
||||
grandparentElement.replaceWith(newTextElement);
|
||||
});
|
||||
}
|
||||
|
||||
// Create a new MutationObserver instance
|
||||
var observer = new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (mutation) {
|
||||
if (mutation.addedNodes.length) {
|
||||
hideVideoPlayerGrandparent(); // Call the function to hide video players
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Options for the observer (which mutations to observe)
|
||||
var config = { childList: true, subtree: true };
|
||||
|
||||
// Start observing the target node for configured mutations
|
||||
observer.observe(document.body, config);
|
||||
|
||||
// Call the function initially to hide any video players on initial load
|
||||
hideVideoPlayerGrandparent();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
545
src/rosenrot.c
|
|
@ -1,545 +0,0 @@
|
|||
#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* global_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(global_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), "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);
|
||||
if (style_js == NULL) {
|
||||
fprintf(stderr, "Failed to allocate memory for style_js\n");
|
||||
return;
|
||||
}
|
||||
read_style_js(style_js);
|
||||
if (style_js != NULL) {
|
||||
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); /*fallthrough */
|
||||
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 */
|
||||
/* Shared web context for all views (needed for web extensions) */
|
||||
static WebKitWebContext* shared_web_context = NULL;
|
||||
|
||||
static WebKitWebContext* get_shared_web_context()
|
||||
{
|
||||
if (shared_web_context == NULL) {
|
||||
shared_web_context = webkit_web_context_new();
|
||||
|
||||
/* Configure web extensions for adblock if enabled */
|
||||
if (ADBLOCK_ENABLED) {
|
||||
webkit_web_context_set_web_process_extensions_directory(shared_web_context, ADBLOCK_EXTENSIONS_DIR);
|
||||
|
||||
/* Pass configuration to the extension */
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
|
||||
g_variant_builder_add(&builder, "{sv}", "enabled", g_variant_new_boolean(TRUE));
|
||||
webkit_web_context_set_web_process_extensions_initialization_user_data(
|
||||
shared_web_context, g_variant_builder_end(&builder));
|
||||
}
|
||||
}
|
||||
return shared_web_context;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* Get shared web context (configured with web extensions if adblock enabled) */
|
||||
WebKitWebContext* context = get_shared_web_context();
|
||||
|
||||
WebKitWebView* view = g_object_new(WEBKIT_TYPE_WEB_VIEW, "settings", settings, "network-session", network_session, "user-content-manager", contentmanager, "web-context", context, 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), 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(global_notebook);
|
||||
NULLCHECK(global_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(global_notebook);
|
||||
int k = gtk_notebook_get_current_page(global_notebook);
|
||||
int o = (n + k - 1) % n;
|
||||
gtk_notebook_set_current_page(global_notebook, o);
|
||||
break;
|
||||
case next_tab:;
|
||||
int m = gtk_notebook_get_n_pages(global_notebook);
|
||||
int l = gtk_notebook_get_current_page(global_notebook);
|
||||
int p = (l + 1) % m;
|
||||
gtk_notebook_set_current_page(global_notebook, p);
|
||||
break;
|
||||
case close_tab:
|
||||
num_tabs -= 1;
|
||||
switch(num_tabs){
|
||||
case 0:
|
||||
exit(0);
|
||||
break;
|
||||
case 1:
|
||||
gtk_notebook_set_show_tabs(global_notebook, false);
|
||||
// fallthrough
|
||||
default:
|
||||
gtk_notebook_remove_page(global_notebook, gtk_notebook_get_current_page(global_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(global_notebook, _SEARCH);
|
||||
break;
|
||||
case show_finder:
|
||||
toggle_bar(global_notebook, _FIND);
|
||||
break;
|
||||
case filter:
|
||||
toggle_bar(global_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(global_notebook, NULL);
|
||||
gtk_notebook_set_show_tabs(global_notebook, true);
|
||||
toggle_bar(global_notebook, _SEARCH);
|
||||
break;
|
||||
|
||||
case hide_bar:
|
||||
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
|
||||
toggle_bar(global_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);
|
||||
if (readability_js == NULL) {
|
||||
fprintf(stderr, "Failed to allocate memory for readability_js\n");
|
||||
break;
|
||||
}
|
||||
read_readability_js(readability_js);
|
||||
if (readability_js != NULL) {
|
||||
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), brave_command, uri);
|
||||
if(system(cmd) == -1) printf("Error opening in brave browser");
|
||||
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) {
|
||||
|
||||
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
|
||||
global_notebook = GTK_NOTEBOOK(gtk_notebook_new());
|
||||
gtk_notebook_set_show_tabs(global_notebook, false);
|
||||
gtk_notebook_set_show_border(global_notebook, false);
|
||||
gtk_window_set_child(window, GTK_WIDGET(global_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), global_notebook);
|
||||
g_signal_connect(GTK_WIDGET(window), "destroy", G_CALLBACK(exit), global_notebook);
|
||||
|
||||
// Load first tab
|
||||
char* first_uri = argc > 1 ? argv[1] : HOME;
|
||||
notebook_create_new_tab(global_notebook, first_uri);
|
||||
|
||||
// Show to user
|
||||
// The first two commands might be 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(global_notebook, true);
|
||||
for (int i = 2; i < argc; i++) {
|
||||
notebook_create_new_tab(global_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;
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
* {
|
||||
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;
|
||||
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
* {
|
||||
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;
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
DEPS='webkit2gtk-4.0'
|
||||
INCS=`pkg-config --cflags ${DEPS}`
|
||||
LIBS=`pkg-config --libs ${DEPS}`
|
||||
echo $INCS
|
||||
echo $LIBS
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
# 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 build
|
||||
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"
|
||||
|
Before Width: | Height: | Size: 739 KiB |
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Exec=/bin/rosenrot %u
|
||||
Name=Rosenrot
|
||||
Comment=Minimalistic browser
|
||||
Icon=/opt/rosenrot/rosenrot-desktop-icon.png
|
||||
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 175 KiB |
|
|
@ -1,37 +0,0 @@
|
|||
@define-color Surface0 #313244;
|
||||
@define-color Surface1 #45475a;
|
||||
@define-color Base #1e1e2e;
|
||||
@define-color Mantle #181825;
|
||||
@define-color Lavender #b4befe;
|
||||
@define-color Text #cdd6f4;
|
||||
|
||||
* {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
outline-color: @Lavender;
|
||||
color: @Text;
|
||||
border-bottom-color: @Base;
|
||||
}
|
||||
|
||||
window, notebook, headerbar {
|
||||
background: @Base;
|
||||
}
|
||||
|
||||
tabs {
|
||||
background-color: @Base;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
tab {
|
||||
background-color: @Base;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
entry {
|
||||
background-color: @Surface0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
entry:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
@define-color Surface0 #313244;
|
||||
@define-color Surface1 #45475a;
|
||||
@define-color Base #1e1e2e;
|
||||
@define-color Mantle #181825;
|
||||
@define-color Lavender #b4befe;
|
||||
@define-color Text #cdd6f4;
|
||||
|
||||
* {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
outline-color: @Lavender;
|
||||
color: @Text;
|
||||
border-color: white;
|
||||
font-size: 25px;
|
||||
/*@Base; */
|
||||
/* border-bottom-color: @Base; */
|
||||
}
|
||||
|
||||
window, notebook, headerbar {
|
||||
background: @Base;
|
||||
}
|
||||
|
||||
/* Make titlebar pretty gigantic. I'm pretty myopic. */
|
||||
.titlebar {
|
||||
padding: 10px;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
|
||||
.titlebar * {
|
||||
padding: 10px;
|
||||
font-size: 27px;
|
||||
}
|
||||
|
||||
header * {
|
||||
font-size: 20px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
tabs {
|
||||
background-color: @Base;
|
||||
padding: 3px;
|
||||
outline-color: white;
|
||||
border-color: @Base;
|
||||
}
|
||||
|
||||
tab {
|
||||
background-color: @Base;
|
||||
margin: 2px 5px 2px 0px;
|
||||
padding: 5px;
|
||||
border-style: solid;
|
||||
font-size: 27px;
|
||||
/*border-color: white;
|
||||
border-bottom-color: white;
|
||||
outline-color: white;
|
||||
margin: 5px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px; */
|
||||
}
|
||||
|
||||
entry {
|
||||
background-color: @Surface0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
entry:focus {
|
||||
background-color: @Surface0;
|
||||
padding-left: 10px;
|
||||
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
Code in this directory refers to a previous version of rosenrot which used the libwebkit2gtk-4.0 api. It is deprecated. See the debian folder for how to use the -4.1 version instead. Package names might vary.
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# Key dependencies
|
||||
sudo apt install git vim gcc make
|
||||
sudo apt install libwebkit2gtk-4.0-dev
|
||||
|
||||
# Optional adblock
|
||||
git clone https://github.com/jun7/wyebadblock
|
||||
cd wyebadblock
|
||||
sudo apt install gstreamer1.0-plugins-good gstreamer1.0-libav
|
||||
make
|
||||
sudo make install
|
||||
cd ..
|
||||
mkdir -p ~/.config/wyebadblock
|
||||
cd ~/.config/wyebadblock
|
||||
wget https://easylist.to/easylist/easylist.txt
|
||||
cd -
|
||||
|
||||
cd ../..
|
||||
make build # or just make
|
||||
sudo make install
|
||||
|
||||
cd -
|
||||
# Ubuntu desktop icon
|
||||
chmod +x rose.desktop
|
||||
sudo cp rose.desktop /usr/share/applications
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Exec=/home/nuno/Documents/workspace/rosenrot/user-scripts/ubuntu-20.04/rose.sh %u
|
||||
Name=Rose
|
||||
Comment=Minimalistic browser
|
||||
Icon=/home/nuno/Documents/workspace/rosenrot/user-scripts/ubuntu-20.04/rose-images/rose-desktop-icon.png
|
||||
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 474 KiB |
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Exec=/bin/rose %u
|
||||
Name=Rose
|
||||
Comment=Minimalistic browser
|
||||
Icon=/home/nuno/Documents/workspace/rosenrot/user-scripts/ubuntu-20.04/rose-images/rose-desktop-icon.png
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/sh
|
||||
GIO_MODULE_DIR=/usr/lib/x86_64-linux-gnu/gio/modules/ /bin/rose "$1"
|
||||
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# Key dependencies
|
||||
sudo apt install libwebkitgtk-6.0-dev
|
||||
sudo apt install libgtk-4-dev
|
||||
|
||||
# Optional adblock
|
||||
# Doesn't work with webkit-6.0
|
||||
# cd ~/.local
|
||||
# git clone https://github.com/jun7/wyebadblock
|
||||
# cd wyebadblock
|
||||
# sudo apt install libglib2.0-dev gstreamer1.0-plugins-good gstreamer1.0-libav
|
||||
# WEBKITVER=6.0 make
|
||||
# sudo WEBKITVER=6.0 make install
|
||||
# cd -
|
||||
|
||||
# cd ..
|
||||
# mkdir -p ~/.config/wyebadblock
|
||||
# cd ~/.config/wyebadblock
|
||||
# wget https://easylist.to/easylist/easylist.txt
|
||||
# cd -
|
||||
|
||||
cd ../..
|
||||
make build
|
||||
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"
|
||||
|
Before Width: | Height: | Size: 739 KiB |
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env xdg-open
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Exec=/bin/rosenrot %u
|
||||
Name=Rosenrot
|
||||
Comment=Minimalistic browser
|
||||
Icon=/opt/rosenrot/rosenrot-desktop-icon.png
|
||||
23
user-scripts/ubuntu-20.04/install-with-dependencies.sh
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Dependencies
|
||||
sudo apt install libwebkit2gtk-4.0-dev
|
||||
sudo apt install clang
|
||||
# sudo apt instal sudo apt install gstreamer1.0-plugins-good gstreamer1.0-libav
|
||||
|
||||
# Adblock
|
||||
git clone https://github.com/jun7/wyebadblock
|
||||
cd wyebadblock
|
||||
make
|
||||
sudo make install
|
||||
cd ..
|
||||
|
||||
# Rose config
|
||||
user=$(whoami)
|
||||
mkdir -p /home/$user/.cache/rose
|
||||
cp ../../config.def.h ../../config.h # you should also probably customize this yourself.
|
||||
sed "s/fenze/$user/g" ../../config.h
|
||||
|
||||
sudo bash ../../install.sh
|
||||
|
||||
# Ubuntu desktop icon
|
||||
chmod +x rose.desktop
|
||||
sudo cp rose.desktop /usr/share/applications
|
||||
13
user-scripts/ubuntu-20.04/notes-desktop.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
For some reason, the Ubuntu environment assigns a program to the rose desktop tab if it is called from within /usr/bin/rose, even if from the Whatsapp desktop file.
|
||||
|
||||
The solution is to make a syslink from rose to whatsapp:
|
||||
|
||||
```
|
||||
$ ln -s /usr/bin/rose /usr/bin/whatsapp
|
||||
```
|
||||
|
||||
and then call whatsapp from:
|
||||
|
||||
```
|
||||
/usr/bin/whatsapp https://web.whatsapp.com/
|
||||
```
|
||||