Compare commits

..

No commits in common. "tabs" and "master" have entirely different histories.
tabs ... master

87 changed files with 6950 additions and 760 deletions

View File

@ -1,12 +0,0 @@
root = true
[*]
indent_style = tab
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
charset = utf-8
trim_trailing_whitespace = true

View File

@ -1,25 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- WebKit [version]
- Gtk [version]
**Additional context**
Add any other context about the problem here.

36
.github/readme.md vendored
View File

@ -1,36 +0,0 @@
<h1 align="center">Rose Browser</h1>
<p align="center">Simple browser based on webkit2-5.0/GTK4.</p>
## Showcase
<img src="https://github.com/mini-rose/rose/blob/master/screenshots/homepage.png?raw=true">
<img src="https://github.com/mini-rose/rose/blob/master/screenshots/youtube.png?raw=true">
## Requirements
In order to build rose you need gtk4 and webkit2gtk-5.0.
In order to use the functionalities:
- video support: gst-libav, gst-plugins-good
- searchbar/find/history: dmenu
- tabs: tabbed
- download: aria2c
## Installation
Run following command to build and install
(if necessary as root):
```$ make clean install```
## Usage
To set default homepage change config.h
Opening new window with specific website:
```$ rose https://duckduckgo.com```
## TODO
- [ ] Add tabs option in config
- [ ] Improve mass weight image loading time
- [ ] Add support for xdg-open and opening files

7
.gitignore vendored
View File

@ -1,4 +1,3 @@
compile_commands.json rosenrot
rose # don't save the binary file, as it doesn't play nicely with https://difftastic.wilfred.me.uk
.cache/ webkit/
/config.h

117
README.md Normal file
View File

@ -0,0 +1,117 @@
# Rosenrot
Rosenrot is a small browser forked from an earlier version of [rose](https://github.com/mini-rose/rose). It has some additional quality of life improvements tailored to my (@NunoSempere) tastes and setup, and detailed installation instructions for Debian 12. Because of the ease of hackability, the minimalism, the unobstrusiveness, it gives me a more comfortable way of navigating the web than normal browsers.
![](https://raw.githubusercontent.com/NunoSempere/rosenrot-browser/master/images/3-init.png)
![](https://raw.githubusercontent.com/NunoSempere/rosenrot-browser/master/images/7-hello-world-search.png)
![](https://raw.githubusercontent.com/NunoSempere/rosenrot-browser/master/images/6-multiple-tabs.png)
### Installation and usage
You can see detailed instructions [here](./user-scripts/debian-12/), for Debian 12 in particular—though they should generalize easily to other distributions.
The general steps are to install dependencies, and then
```
make build # by default using webkitgtk6/gtk4; see also build3
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
- For now, both GTK3 and GTK4-based versions—eventually only the GTK4 version will remain
You can see some screenshots in the [images](./images) folder.
## 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 InitialSerenityOS, it later became its own project. Uses its own html and javascript engine. Compiling it on a mainstream linux distribution is now doable.
- [servo](https://github.com/servo/servo). Firefox/Mozilla. An in-development browser engine written in Rust, meant to replace Gecko. Could be extremely cool once it is ready, but it has been many years in development.
### Relationship with [rose](https://github.com/mini-rose/rose)
- Rose is a small browser based on webkit2gtk. Previously, it described itself as aiming to be a "basement for creating your own browser using [the] gtk and webkit libraries". It has since diverged into a more featureful small browser with lua bindings, and rebased its history. You can see the original, minimal version [here](https://github.com/NunoSempere/rosenrot-browser/blob/a45d1c70f58586fed97df70650e5d066b73d0a0d/rose.c).
- Rosenrot is my (@NunoSempere's) fork from that earlier minimal rose, the GTK 3 version. It has accumulated quality of life features and, honestly, cruft, that I like, like a "readability" plugin that simplifies annoying websites like [Matt Levine's Money Stuff newsletter](https://www.bloomberg.com/opinion/articles/2022-10-18/matt-levine-s-money-stuff-credit-suisse-was-a-reverse-meme-stock). It also incorporates ad-blocking.
- rose updated to allow compilation with an up-to-date version of webkit on both GTK3 and GTK4 earlier, though rosenrot now offers this as well.
- Rosenrot is also a song by the German hardcore rock band [Rammstein](https://www.youtube.com/watch?v=af59U2BRRAU).
### Comparison with [surf](https://git.suckless.org/surf/file/surf.c.html)
- 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.
- Like rosenrot until not so long ago, it [uses](https://git.suckless.org/surf/file/config.mk.html#l15) an obsolete & deprecated version of [webkit](https://blogs.gnome.org/mcatanzaro/2023/03/21/webkitgtk-api-for-gtk-4-is-now-stable/) (2.40, with GTK3)
- My recommendation would be to use rosenrot, and if you find some feature missing, either look how surf does it and import it to rose, or move to surf. But then again, I've built rosenrot to cater to my own tastes, so I'd say that.
## Folk wisdom
Of general interest:
- I just found out that you can inspect a GTK application with the GTK explorer if you set a certain command-line variable. Try this with `make inspect`.
- Static variables keep their value between invocations.
- By default the searchbar is pretty gigantic. I've made this so because I'm a bit myopic, but also work with my laptop in a laptop stand. Anyways, if you are a more normal person you can change this in the style/style-gtk4.css style/style-gtk3.css files.
- The gtk style usage isn't updated until installation. This is because by default rose uses the theme located in /opt/rosenrot/style-gtk3/4.css, and that file isn't updated until make install.
- The [min](https://git.nunosempere.com/open.source/rosenrot/src/branch/min) branch contains a minimalistic version of rosenrot, in one c file with 320 lines (256 without comments and extra newlines). It might be of interest to developers and those seeking to understand the code.
The "architecture" of the application looks as follows:
![](https://raw.githubusercontent.com/NunoSempere/rosenrot-browser/master/images/0-architecture.png)
### webkit2gtk-4.0 vs webkit2gtk-4.1 vs webkit2gtk-6.0
See [this blog post](https://blogs.gnome.org/mcatanzaro/2023/03/21/webkitgtk-api-for-gtk-4-is-now-stable/) for details. webkit2gtk-4.0 is deprecated, webkit2gtk-4.1 is the current [stable](https://webkitgtk.org/reference/webkit2gtk/stable/index.html) release and uses GTK3. webkit2gtk-6.0 is the current [unstable](https://webkitgtk.org/reference/webkitgtk/unstable/index.html) release, and uses GTK4.
Migration instructions for migration to webkit2gtk-6 and GTK4 can be seen [here](https://github.com/WebKit/WebKit/blob/ed1422596dce5ff012e64a38faf402ac1674fc7e/Source/WebKit/gtk/migrating-to-webkitgtk-6.0.md) and [here](https://docs.gtk.org/gtk4/migrating-3to4.html).
Rosenrot currently supports both the stable webkit2gtk-4.1/GTK3 release, and a newer release using webkit2gtk-6.0/GTK4. Eventually the later will become the only version. Readers might want to look through the history to see a bit about the updating process; commits related to the transition are tagged with the "GTK4: " label.
### Ubuntu 20.04
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.

70
TODO.md Normal file
View File

@ -0,0 +1,70 @@
# To do
- [ ] Move to a later C standard (C11?) and use safer string handling functions provided by it.
- The thing is, I kinda feel attached to C89-C99
- [ ] Consider
- See make lint for purported insecurities
- [ ] Document creating new applications, e.g., as in [Asana for Linux](https://git.nunosempere.com/NunoSempere/asana-for-linux)
- [ ] This time, use something other than Whatsapp as an example syslink.
- [ ] Fix bug about distorted audio. Maybe related to [this pipewire issue](<https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/1547>)?
- See whether it even exists at all
- [ ] 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/

View File

@ -1,35 +0,0 @@
#include "keyconf.h"
static const char *options[] = {
[CACHE] = DEFAULT,
[HOMEPAGE] = DEFAULT,
};
static int appearance[] = {
[HEIGHT] = DEFAULT,
[WIDTH] = DEFAULT,
[DARKMODE] = TRUE
};
static const Key keys[] = {
{ MODKEY, GDK_KEY_h, goback },
{ MODKEY, GDK_KEY_l, goforward },
{ MODKEY, GDK_KEY_y, copy_url },
{ MODKEY, GDK_KEY_p, paste_url },
{ MODKEY, GDK_KEY_e, search },
{ MODSFT, GDK_KEY_K, zoomin },
{ MODSFT, GDK_KEY_J, zoomout },
{ MODSFT, GDK_KEY_Q, zoomreset },
{ MODKEY, GDK_KEY_i, inspector },
{ MODKEY, GDK_KEY_j, down },
{ MODKEY, GDK_KEY_k, up },
{ MODKEY, GDK_KEY_f, find },
{ MODKEY, GDK_KEY_n, findnext },
{ MODSFT, GDK_KEY_N, findprev },
{ MODKEY, GDK_KEY_r, reload },
{ MODSFT, GDK_KEY_R, reloadforce },
{ NOMODK, GDK_KEY_F11, fullscreen },
{ MODSFT, GDK_KEY_H, history },
{ MODKEY, GDK_KEY_g, gotop },
{ MODSFT, GDK_KEY_G, gobottom }
};

144
config.h Normal file
View File

@ -0,0 +1,144 @@
#include <stdbool.h>
#include <gtk/gtk.h>
/* Key user config */
#define HEIGHT 1080
#define FULL_WIDTH 1920
#define WIDTH FULL_WIDTH
#define BAR_WIDTH FULL_WIDTH/2
/* More user config */
#define ZOOM_START_LEVEL 1.8
#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
/*
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
/* 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 },
// working hypothesis: Page_UP vs KP_Page_Up might depend on whether the user has a numpad
{ CTRL | SFT, KEY(Page_Down), next_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 }
};

BIN
images/0-architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
images/1-startup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

BIN
images/2-blog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

BIN
images/3-init.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
images/6-multiple-tabs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

View File

@ -0,0 +1 @@
https://www.onlygfx.com/red-rose-png-image-transparent/

BIN
images/flower-imgs/rose.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 KiB

View File

@ -1,51 +0,0 @@
#pragma once
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <stdbool.h>
#define ALTKEY GDK_ALT_MASK
#define MODKEY GDK_CONTROL_MASK
#define MODSFT 5
#define NOMODK 0
#define CACHE 0
#define HOMEPAGE 1
#define DEFAULT 0
#define HEIGHT 0
#define WIDTH 1
#define DARKMODE 2
#define SCROLLBARS 3
typedef struct {
unsigned modkey;
unsigned keycod;
unsigned funcid;
} Key;
enum {
goback,
goforward,
copy_url,
paste_url,
fullscreen,
inspector,
search,
find,
findnext,
findprev,
zoomin,
zoomout,
zoomreset,
down,
up,
reload,
reloadforce,
history,
gotop,
gobottom,
tabnext,
tabprev
};

21
license Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022-2024 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.

148
makefile
View File

@ -1,51 +1,123 @@
# -*- indent-tabs-mode: t -*- # C compiler
CC ?= clang CC=gcc # alternatives: tcc, clang, zig cc
CFLAGS = `pkg-config --cflags gtk4 webkit2gtk-5.0 x11` WARNINGS=-Wall
LIBS = `pkg-config --libs gtk4 webkit2gtk-5.0 x11` OPTIMIZED_SOME=-O3
OPTIONS = -Dgtk_doc=false -Dintrospection=false \ OPTIMIZED_MORE=-Ofast -march=native -funit-at-a-time -flto # binary will not be compatible with other computers, but may be much faster
-DG_DISABLE_CAST_CHECKS \ DEBUG=#-g
-Dcloudproviders=false \ STD=-std=c99 # maybe consider moving to c11 and using safer string handling
-DGTK_DISABLE_DEPRECATED \
-DGDK_PIXBUF_DISABLE_DEPRECATED \
-DG_DISABLE_DEPRECATED -DENABLE_GLES2=ON \
-DENABLE_GEOLOCATION=OFF -DENABLE_PLUGIN_PROCESS_GTK2=OFF \
-DPORT=GTK \
-Dforce_posix_threads=true \
-Dman=false \
-Dxinerama=false \
-Dcolord=false \
-Db_coverage=false \
-Ddebug=false
PREFIX ?= /usr/local # Dependencies for WebkitGTK4/GTK3
SRC_3=rosenrot3.c
DEPS_3='webkit2gtk-4.1'
INCS_3=`pkg-config --cflags ${DEPS_3}`
LIBS_3=`pkg-config --libs ${DEPS_3}`
all: config.h rose # Dependencies for WebkitGTK6/GTK4
SRC_4=rosenrot4.c
DEPS_4='webkitgtk-6.0'
INCS_4=`pkg-config --cflags ${DEPS_4}` `pkg-config --cflags gtk4`
LIBS_4=`pkg-config --libs ${DEPS_4}` `pkg-config --libs gtk4`
DEPRECATION_FLAGS=-DGDK_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED
rose: # User config
$(CC) -fPIC -O3 -o rose *.c $(CFLAGS) $(LIBS) $(OPTIONS) CONFIG=config.h
strip ./rose
debug: # Plugins
$(CC) -fPIC -o rose *.c $(CFLAGS) $(LIBS) -Wall -Wextra -fsanitize=address -DG_DISABLE_CAST_CHECKS include plugins/plugins.mk
# PLUGINS=./plugins/stand_in/stand_in.c
ADBLOCK='-L/usr/lib/wyebrowser/adblock.so' # optional adblocking; depends on https://github.com/jun7/wyebadblock
config.h: ## Formatter
[ -f "$@" ] || cp config.def.h $@ STYLE_BLUEPRINT="{BasedOnStyle: webkit, AllowShortIfStatementsOnASingleLine: true, IndentCaseLabels: true, AllowShortEnumsOnASingleLine: true}"
FORMATTER_C=clang-format -i -style=$(STYLE_BLUEPRINT)
FORMATTER_JS=npx prettier -w
install: all # Runtime files
cp -f ./rose $(PREFIX)/bin/rose MAINTAINER_CACHE_DIR=/home/nuno/.cache/rosenrot
cp -f ./scripts/dmenu_rose.sh $(PREFIX)/bin/dmenu_rose USER_CACHE_DIR=/home/`whoami`/.cache/rosenrot
RUNTIME_FILES_DIR=/opt/rosenrot/
build: $(SRC_4) $(PLUGINS) $(CONFIG) constants user_cache
$(CC) $(STD) $(WARNINGS) $(DEPRECATION_FLAGS) $(OPTIMIZED_MORE) $(DEBUG) $(INCS_4) $(PLUGINS) $(SRC_4) -o rosenrot $(LIBS_4) $(ADBLOCK)
@echo
build3: $(SRC_3) $(PLUGINS) $(CONFIG) constants user_cache
$(CC) $(STD) $(WARNINGS) $(OPTIMIZED_MORE) $(DEBUG) $(INCS_3) $(PLUGINS) $(SRC_3) -o rosenrot $(LIBS_3) $(ADBLOCK)
@echo
format: $(SRC_3) $(SRC_4) $(PLUGINS)
$(FORMATTER_C) $(SRC_3) $(PLUGINS) $(config.h)
$(FORMATTER_C) $(SRC_4_greenfield) $(PLUGINS) $(config.h)
$(FORMATTER_JS) plugins/readability/readability.js
$(FORMATTER_JS) plugins/style/style.js
# Installation
install: rosenrot runtime_files
cp -f rosenrot /usr/bin
cp rosenrot-mklink /usr/bin
@echo
uninstall: uninstall:
rm -f $(PREFIX)/bin/rose $(PREFIX)/bin/dmenu_rose rm -r /opt/rosenrot
rm /usr/bin/rosenrot
rm /usr/bin/rosenrot-mklink
rm $(USER_CACHE_DIR)
clean: clean:
rm -f rose compile_flags.txt rm rosenrot
rm $(USER_CACHE_DIR)
clean-all: clean constants:
rm -f config.h @echo
@echo "# Computing constants"
cd plugins/readability/ && sh recompute_READABILITY_N.sh
cd plugins/style && sh recompute_STYLE_N.sh
@echo
flags: user_cache:
echo $(CFLAGS) | sed 's/ /\n/g' > compile_flags.txt @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
.PHONY: all rose clean clean-all install uninstall flags config.h runtime_files:
.SILENT: all rose clean clean-all install uninstall flags config.h @echo
sudo mkdir -p /opt/rosenrot/
sudo cp styles-gtk/style-gtk3.css /opt/rosenrot/
sudo cp styles-gtk/style-gtk4.css /opt/rosenrot/
sudo touch /opt/rosenrot/uris.txt
sudo chmod a+rw /opt/rosenrot/uris.txt
sudo cp -r images/flower-imgs /opt/rosenrot/
sudo cp plugins/style/style.js /opt/rosenrot/
sudo cp plugins/readability/readability.js /opt/rosenrot/
# More misc recipes
lint:
clang-tidy $(SRC) $(PLUGINS) -- -Wall -O3 $(INCS) -o rosenrot $(LIBS)
fast: $(SRC) $(PLUGINS) $(CONFIG)
rm -f *.gcda
GIO_MODULE_DIR=/usr/lib/x86_64-linux-gnu/gio/modules/
$(CC) $(WARNINGS) $(OPTIMIZED_MORE) -fprofile-generate $(INCS_4) $(PLUGINS) $(SRC_4) -o rosenrot $(LIBS_4) $(ADBLOCK)
@echo "Now use the browser for a while to gather some profiling data"
sleep 2
./rosenrot
$(CC) $(WARNINGS) $(OPTIMIZED_MORE) -fprofile-use $(INCS_4) $(PLUGINS) $(SRC_4) -o rosenrot $(LIBS_4) $(ADBLOCK)
rm -f *.gcda
inspect: rosenrot
GTK_DEBUG=interactive ./rosenrot
diagnose_deprecations: rosenrot
G_ENABLE_DIAGNOSTIC=1 ./rosenrot
view-gtk3-version:
dpkg -l libgtk-3-0
twitter:
sudo mkdir -p /usr/bin/rosenrot-browser
sudo cp rosenrot /usr/bin/rosenrot-browser/twitter

View File

@ -0,0 +1,3 @@
## About
This code automatically redirects webpage to their open-source frontends. It is based on <https://libredirect.codeberg.page/>

View File

@ -0,0 +1,72 @@
#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");
}
}
strcpy(output, uri);
}
return 0;
}

View File

@ -0,0 +1,5 @@
#pragma once
#define LIBRE_N 50
int libre_redirect(const char* uri, char* uri_filtered);

View File

@ -0,0 +1,11 @@
#!/bin/bash
CC=gcc
FLAGS="-std=c99 -Wall -lm"
SRC=example.c
REQS="../str_replace_start.c ../libre_redirect.c"
echo -e "\n\n\n"
$CC $FLAGS $SRC $REQS -o example

View File

@ -0,0 +1,19 @@
#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.
}
}

5
plugins/plugins.h Normal file
View File

@ -0,0 +1,5 @@
#include "strings/strings.h"
#include "libre_redirect/libre_redirect.h"
#include "readability/readability.h"
#include "shortcuts/shortcuts.h"
#include "style/style.h"

15
plugins/plugins.mk Normal file
View File

@ -0,0 +1,15 @@
## Shared
COMMON_CODE=./plugins/strings/strings.c
## Plugins
CUSTOM_STYLES=./plugins/style/style.c
SHORTCUTS=./plugins/shortcuts/shortcuts.c
READABILITY=./plugins/readability/readability.c
LIBRE_REDIRECT=./plugins/libre_redirect/libre_redirect.c
STAND_IN=./plugins/stand_in/stand_in.c # gives function definitions for the above, which do nothing
PLUGINS=$(COMMON_CODE) $(CUSTOM_STYLES) $(SHORTCUTS) $(READABILITY) $(LIBRE_REDIRECT)
# PLUGINS=$(STAND_IN)

View File

@ -0,0 +1,2 @@
This code reimplements firefox readability mode. Code taken from <https://raw.githubusercontent.com/ushnisha/readability-reader-webextensions/master/content_scripts/tranquilize.js>

BIN
plugins/readability/readability Executable file

Binary file not shown.

View File

@ -0,0 +1,31 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define READABILITY_N 88097 + 1000
void read_readability_js(char* string)
{
FILE* fp = fopen("/opt/rosenrot/readability.js", "r");
if (!fp) { // fp is NULL, fopen failed
fprintf(stderr, "Failed to open file\n");
fprintf(stderr, "Consider running $ sudo make runtime_files\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);
}
*/

View File

@ -0,0 +1,5 @@
#pragma once
#define READABILITY_N 88097 + 1000
void read_readability_js(char* string);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
#!/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"

View File

@ -0,0 +1,7 @@
## 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

View File

@ -0,0 +1,69 @@
#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");
}
}
strcpy(output, uri);
}
if (DEBUG) printf("No match found\n\n");
return 0;
}

View File

@ -0,0 +1,5 @@
#pragma once
#define SHORTCUT_N 41
int shortcut_expand(const char* uri, char* output);

View File

@ -0,0 +1,30 @@
/* 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;
}

View File

@ -0,0 +1,24 @@
/* 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);

61
plugins/strings/strings.c Normal file
View File

@ -0,0 +1,61 @@
#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] = '\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");
strcpy(output, string);
}
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
*/

View File

@ -0,0 +1,4 @@
#pragma once
void str_init(char* str, int n);
int str_replace_start(const char* string, const char* target, const char* replacement, char* output);

5
plugins/style/README.md Normal file
View File

@ -0,0 +1,5 @@
## 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.

View File

@ -0,0 +1,9 @@
#!/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"

21
plugins/style/style.c Normal file
View File

@ -0,0 +1,21 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STYLE_N 9278 + 1000
void read_style_js(char* string)
{
FILE* fp = fopen("/opt/rosenrot/style.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);
}

5
plugins/style/style.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#define STYLE_N 9278 + 1000
void read_style_js(char* string);

346
plugins/style/style.js Normal file
View File

@ -0,0 +1,346 @@
// 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
{
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";
}
});
}

76
rose.c
View File

@ -1,76 +0,0 @@
#include "rose.h"
#define MSGBUFSZ 8
#define LENGTH(x) (sizeof(x) / sizeof(x[0]))
Display *glob_dpy; /* defined in rose.h */
static guint glob_xid;
static Atom glob_atoms[AtomLast];
void setatom(int a, const char *v)
{
XChangeProperty(glob_dpy, glob_xid,
glob_atoms[a], glob_atoms[AtomUTF8], 8, PropModeReplace,
(unsigned char *)v, strlen(v) + 1);
XSync(glob_dpy, False);
}
const char* getatom(int a)
{
static char buf[BUFSIZ];
Atom adummy;
int idummy;
unsigned long ldummy;
unsigned char *p = NULL;
XSync(glob_dpy, False);
XGetWindowProperty(glob_dpy, glob_xid,
glob_atoms[a], 0L, BUFSIZ, False, glob_atoms[AtomUTF8],
&adummy, &idummy, &ldummy, &ldummy, &p);
if (p)
strncpy(buf, (char *)p, LENGTH(buf) - 1);
else
buf[0] = '\0';
XFree(p);
return buf;
}
static void setup()
{
if (!(glob_dpy = XOpenDisplay(NULL))) {
puts("Can't open default display");
exit(1);
}
glob_atoms[AtomFind] = XInternAtom(glob_dpy, "_ROSE_FIND", False);
glob_atoms[AtomGo] = XInternAtom(glob_dpy, "_ROSE_GO", False);
glob_atoms[AtomUri] = XInternAtom(glob_dpy, "_ROSE_URI", False);
}
static void run(GtkApplication *app)
{
RoseWindow *window = rose_window_new(app);
if (appearance[DARKMODE])
g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", true, NULL);
if (!options[HOMEPAGE])
options[HOMEPAGE] = "https://duckduckgo.com";
glob_xid = rose_window_show(app, window, options[HOMEPAGE]);
}
int main(int argc, char **argv)
{
if (argc == 2) {
options[HOMEPAGE] = argv[1];
argv++; argc--;
}
setup();
GtkApplication *app = gtk_application_new("org.gtk.rose", G_APPLICATION_NON_UNIQUE);
g_signal_connect(app, "activate", G_CALLBACK(run), NULL);
g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
}

26
rose.h
View File

@ -1,26 +0,0 @@
#pragma once
#include "window.h"
#include "config.h"
#include <gtk/gtk.h>
#include <gdk/x11/gdkx.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <stdlib.h>
#include <webkit2/webkit2.h>
#include <sys/wait.h>
enum {
AtomFind,
AtomGo,
AtomUri,
AtomUTF8,
AtomLast
};
extern Display *glob_dpy; /* declared in rose.c */
const char* getatom(int a);
void setatom(int a, const char *v);

17
rosenrot-mklink Executable file
View File

@ -0,0 +1,17 @@
#!/bin/sh
test "$1" = "--help" || test -z "$1" && {
printf "%s\n" "usage: rosenrot-mklink <alias> <url>" \
"Create a /usr/bin link to a website."
exit
}
test -z "$2" || {
test -f "/usr/bin/$1" && {
echo "/usr/bin/$1 already exists, remove it first"
exit 1
}
printf "#!/bin/sh\n\nrosenrot %s" "$2" > /usr/bin/$1
chmod +x /usr/bin/$1
}

442
rosenrot3.c Normal file
View File

@ -0,0 +1,442 @@
#include <gdk/gdk.h>
#include <stdlib.h>
#include <string.h>
#include <webkit2/webkit2.h>
#include "config.h"
#include "plugins/plugins.h"
/* Global variables */
static GtkNotebook* notebook;
static GtkWindow* window;
typedef enum { _SEARCH, _FIND, _HIDDEN } Bar_entry_mode;
static struct {
GtkHeaderBar* widget;
GtkEntry* line;
GtkEntryBuffer* line_text;
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);
/* Utils */
WebKitWebView* notebook_get_webview(GtkNotebook* notebook)
{
return WEBKIT_WEB_VIEW(gtk_notebook_get_nth_page(notebook, gtk_notebook_get_current_page(notebook)));
}
/* Load content*/
void load_uri(WebKitWebView* view, const char* uri)
{
bool is_empty_uri = (strlen(uri) == 0);
if (is_empty_uri) {
webkit_web_view_load_uri(view, "");
toggle_bar(notebook, _SEARCH);
return;
}
bool has_direct_uri_prefix = g_str_has_prefix(uri, "http://") || g_str_has_prefix(uri, "https://") || g_str_has_prefix(uri, "file://") || g_str_has_prefix(uri, "about:");
if (has_direct_uri_prefix){
webkit_web_view_load_uri(view, uri);
return;
}
bool has_common_domain_extension = (strstr(uri, ".com") || strstr(uri, ".org"));
if (has_common_domain_extension){
char tmp[strlen("https://") + strlen(uri) + 1];
snprintf(tmp, sizeof(tmp) + 1, "https://%s", uri);
webkit_web_view_load_uri(view, tmp);
return;
}
int l = SHORTCUT_N + strlen(uri) + 1;
char uri_expanded[l];
str_init(uri_expanded, l);
int check = shortcut_expand(uri, uri_expanded);
bool has_shortcut = (check == 2);
if (has_shortcut){
webkit_web_view_load_uri(view, uri_expanded);
return;
}
char tmp[strlen(uri) + strlen(SEARCH)];
snprintf(tmp, sizeof(tmp), SEARCH, uri);
webkit_web_view_load_uri(view, tmp);
}
/* Deal with new load or changed load */
void redirect_if_annoying(WebKitWebView* view, const char* uri)
{
if (LIBRE_REDIRECT_ENABLED) {
int l = LIBRE_N + strlen(uri) + 1;
char uri_filtered[l];
str_init(uri_filtered, l);
int check = libre_redirect(uri, uri_filtered);
if (check == 2) webkit_web_view_load_uri(view, uri_filtered);
}
}
void set_custom_style(WebKitWebView* view)
{
if (custom_style_enabled) {
char* style_js = malloc(STYLE_N + 1);
read_style_js(style_js);
webkit_web_view_evaluate_javascript(view, style_js, -1, NULL, "rosenrot-style-plugin", NULL, NULL, NULL);
free(style_js);
}
}
void handle_signal_load_changed(WebKitWebView* self, WebKitLoadEvent load_event,
GtkNotebook* notebook)
{
switch (load_event) {
// https://webkitgtk.org/reference/webkit2gtk/2.5.1/WebKitWebView.html
case WEBKIT_LOAD_STARTED:
case WEBKIT_LOAD_COMMITTED:
set_custom_style(self);
case WEBKIT_LOAD_REDIRECTED:
redirect_if_annoying(self, webkit_web_view_get_uri(self));
break;
case WEBKIT_LOAD_FINISHED: {
set_custom_style(self);
/* Add gtk tab title */
const char* webpage_title = webkit_web_view_get_title(self);
const int max_length = 25;
char tab_title[max_length + 1];
if (webpage_title != NULL) {
for (int i = 0; i < (max_length); i++) {
tab_title[i] = webpage_title[i];
if (webpage_title[i] == '\0') {
break;
}
}
tab_title[max_length] = '\0';
}
gtk_notebook_set_tab_label_text(notebook, GTK_WIDGET(self),
webpage_title == NULL ? "" : tab_title);
}
}
}
/* New tabs */
WebKitWebView* create_new_webview()
{
char* style;
WebKitSettings* settings = webkit_settings_new_with_settings(WEBKIT_DEFAULT_SETTINGS, NULL);
if (CUSTOM_USER_AGENT) {
webkit_settings_set_user_agent(
settings,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
"like Gecko) Chrome/120.0.0.0 Safari/537.3");
// https://www.useragents.me
}
WebKitWebContext* web_context = webkit_web_context_new_with_website_data_manager(webkit_website_data_manager_new(DATA_MANAGER_OPTS, NULL));
WebKitUserContentManager* contentmanager = webkit_user_content_manager_new();
WebKitCookieManager* cookiemanager = webkit_web_context_get_cookie_manager(web_context);
webkit_cookie_manager_set_persistent_storage(cookiemanager, DATA_DIR "/cookies.sqlite", WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
webkit_cookie_manager_set_accept_policy(cookiemanager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
if (g_file_get_contents("~/opt/rosenrot/style.css", &style, NULL, NULL)) {
webkit_user_content_manager_add_style_sheet(
contentmanager, webkit_user_style_sheet_new(style, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL));
}
WebKitWebView* view = g_object_new(WEBKIT_TYPE_WEB_VIEW, "settings", settings, "web-context", web_context, "user-content-manager", contentmanager, NULL);
return view;
}
GtkWidget* handle_signal_create_new_tab(WebKitWebView* self,
WebKitNavigationAction* navigation_action,
GtkNotebook* notebook)
{
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);
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;
}
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();
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);
gtk_widget_show_all(GTK_WIDGET(window));
gtk_widget_hide(GTK_WIDGET(bar.widget));
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_show(GTK_WIDGET(bar.widget));
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_show(GTK_WIDGET(bar.widget));
gtk_window_set_focus(window, GTK_WIDGET(bar.line));
break;
}
case _HIDDEN:
gtk_widget_hide(GTK_WIDGET(bar.widget));
}
}
// Handle what happens when the user is on the bar and presses enter
void handle_signal_bar_press_enter(GtkEntry* self, GtkNotebook* notebook)
{
if (bar.entry_mode == _SEARCH)
load_uri(notebook_get_webview(notebook), gtk_entry_buffer_get_text(bar.line_text));
else if (bar.entry_mode == _FIND)
webkit_find_controller_search(
webkit_web_view_get_find_controller(notebook_get_webview(notebook)),
gtk_entry_buffer_get_text(bar.line_text),
WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | WEBKIT_FIND_OPTIONS_WRAP_AROUND,
G_MAXUINT);
gtk_widget_hide(GTK_WIDGET(bar.widget));
}
/* Shortcuts */
int handle_shortcut(func id, GtkNotebook* notebook)
{
static double zoom = ZOOM_START_LEVEL;
static bool is_fullscreen = 0;
WebKitWebView* view = notebook_get_webview(notebook);
switch (id) {
case goback:
webkit_web_view_go_back(view);
break;
case goforward:
webkit_web_view_go_forward(view);
break;
case toggle_custom_style: /* Ctrl s + Ctrl Shift R to reload */
custom_style_enabled ^= 1;
// fallthrough
case refresh:
webkit_web_view_reload(view);
break;
case refresh_force:
webkit_web_view_reload_bypass_cache(view);
break;
case back_to_home:
load_uri(view, HOME);
break;
case zoomin:
webkit_web_view_set_zoom_level(view,
(zoom += ZOOM_STEPSIZE));
break;
case zoomout:
webkit_web_view_set_zoom_level(view,
(zoom -= ZOOM_STEPSIZE));
break;
case zoom_reset:
webkit_web_view_set_zoom_level(view,
(zoom = ZOOM_START_LEVEL));
break;
case prev_tab:; // declarations aren't statements
// https://stackoverflow.com/questions/92396/why-cant-variables-be-declared-in-a-switch-statement
int n = gtk_notebook_get_n_pages(notebook);
int k = gtk_notebook_get_current_page(notebook);
int l = (n + k - 1) % n;
gtk_notebook_set_current_page(notebook, l);
break;
case next_tab:;
int m = gtk_notebook_get_n_pages(notebook);
int i = gtk_notebook_get_current_page(notebook);
int j = (i + 1) % m;
gtk_notebook_set_current_page(notebook, j);
break;
case close_tab:
num_tabs -= 1;
switch(num_tabs){
case 0:
exit(0);
break;
case 1:
gtk_notebook_set_show_tabs(notebook, false);
// fallthrough
default:
gtk_notebook_remove_page(notebook, gtk_notebook_get_current_page(notebook));
}
break;
case toggle_fullscreen:
if (is_fullscreen)
gtk_window_unfullscreen(window);
else
gtk_window_fullscreen(window);
is_fullscreen = !is_fullscreen;
break;
case show_searchbar:
toggle_bar(notebook, _SEARCH);
break;
case show_finder:
toggle_bar(notebook, _FIND);
break;
case finder_next:
webkit_find_controller_search_next(webkit_web_view_get_find_controller(view));
break;
case finder_prev:
webkit_find_controller_search_previous(webkit_web_view_get_find_controller(view));
break;
case new_tab:
notebook_create_new_tab(notebook, NULL);
gtk_notebook_set_show_tabs(notebook, true);
toggle_bar(notebook, _SEARCH);
break;
case hide_bar:
toggle_bar(notebook, _HIDDEN);
break;
case halve_window:
gtk_window_resize(window, FULL_WIDTH/2, HEIGHT);
break;
case rebig_window:
gtk_window_resize(window, FULL_WIDTH, HEIGHT);
break;
case prettify: {
if (READABILITY_ENABLED) {
char* readability_js = malloc(READABILITY_N + 1);
read_readability_js(readability_js);
webkit_web_view_evaluate_javascript(view, readability_js, -1, NULL, "rosenrot-readability-plugin", NULL, NULL, NULL);
free(readability_js);
}
break;
}
}
return 1;
}
// Listen to key presses and call shortcuts if needed.
int handle_signal_keypress(void* self, GdkEvent* event, GtkNotebook* notebook)
{
(void)self;
guint event_keyval = 0;
gdk_event_get_keyval(event, &event_keyval);
GdkModifierType event_state = 0;
gdk_event_get_state(event, &event_state);
if (0) {
printf("Keypress state: %d\n", event_state);
printf("Keypress value: %d\n", event_keyval);
}
for (int i = 0; i < sizeof(shortcut) / sizeof(shortcut[0]); i++)
if ((event_state & shortcut[i].mod || shortcut[i].mod == 0x0) && event_keyval == shortcut[i].key)
return handle_shortcut(shortcut[i].id, notebook);
/*
If I wanted to bind button presses, like the extra button in the mouse,
I would have to bind the button-press-event signal instead.
Some links in case I go down that road:
- https://docs.gtk.org/gtk3/signal.Widget.button-press-event.html
- https://docs.gtk.org/gdk3/union.Event.html
- https://docs.gtk.org/gdk3/struct.EventButton.html
*/
return 0;
}
int main(int argc, char** argv)
{
/* Initialize GTK in general */
gtk_init(NULL, NULL); // https://docs.gtk.org/gtk3/func.init.html
g_object_set(gtk_settings_get_default(), GTK_SETTINGS_CONFIG_H, NULL); // https://docs.gtk.org/gobject/method.Object.set.html
GtkCssProvider* css = gtk_css_provider_new();
gtk_css_provider_load_from_path(css, "/opt/rosenrot/style-gtk3.css", NULL);
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(css), 800);
/* Initialize GTK objects. These are declared as static globals at the top of this file */
// Window
window = GTK_WINDOW(gtk_window_new(0));
gtk_window_set_default_size(window, WIDTH, HEIGHT);
// Notebook
notebook = GTK_NOTEBOOK(gtk_notebook_new());
gtk_notebook_set_show_tabs(notebook, false);
gtk_notebook_set_show_border(notebook, false);
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(notebook));
// Bar
bar.line_text = GTK_ENTRY_BUFFER(gtk_entry_buffer_new("", 0));
bar.line = GTK_ENTRY(gtk_entry_new_with_buffer(bar.line_text));
gtk_entry_set_alignment(bar.line, 0.48);
gtk_widget_set_size_request(GTK_WIDGET(bar.line), BAR_WIDTH, -1);
bar.widget = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_header_bar_set_custom_title(bar.widget, GTK_WIDGET(bar.line));
gtk_window_set_titlebar(window, GTK_WIDGET(bar.widget));
// Signals
g_signal_connect(window, "key-press-event", G_CALLBACK(handle_signal_keypress), notebook);
g_signal_connect(window, "destroy", G_CALLBACK(exit), notebook);
g_signal_connect(bar.line, "activate", G_CALLBACK(handle_signal_bar_press_enter), notebook);
/* Load first tab */
char* first_uri = argc > 1 ? argv[1] : HOME;
notebook_create_new_tab(notebook, first_uri);
/* Show to user */
gtk_widget_show_all(GTK_WIDGET(window));
if (argc != 0) gtk_widget_hide(GTK_WIDGET(bar.widget));
/* Deal with more tabs */
if (argc > 2) {
gtk_notebook_set_show_tabs(notebook, true);
for (int i = 2; i < argc; i++) {
notebook_create_new_tab(notebook, argv[i]);
}
}
gtk_main();
}

507
rosenrot4.c Normal file
View File

@ -0,0 +1,507 @@
#include <gdk/gdk.h>
#include <stdlib.h>
#include <string.h>
#include <webkit/webkit.h>
#include "config.h"
#include "plugins/plugins.h"
/* Global variables */
static GtkNotebook* notebook;
static GtkWindow* window;
typedef enum { _SEARCH, _FIND, _FILTER, _HIDDEN } Bar_entry_mode;
static struct {
GtkHeaderBar* widget;
GtkEntry* line;
GtkEntryBuffer* line_text;
Bar_entry_mode entry_mode;
} bar;
static int num_tabs = 0;
static int custom_style_enabled = 1;
/* Forward declarations */
void toggle_bar(GtkNotebook* notebook, Bar_entry_mode mode);
void notebook_create_new_tab(GtkNotebook* notebook, const char* uri);
static int handle_signal_keypress(void* self, int keyval, int keycode,
GdkModifierType state, void* controller);
/* Utils */
WebKitWebView* notebook_get_webview(GtkNotebook* notebook) /* TODO: Think through whether to pass global variables or not */
{
WebKitWebView* view = WEBKIT_WEB_VIEW(gtk_notebook_get_nth_page(notebook, gtk_notebook_get_current_page(notebook)));
NULLCHECK(view);
return view;
}
/* Load content */
void load_uri(WebKitWebView* view, const char* uri)
{
bool is_empty_uri = (strlen(uri) == 0);
if (is_empty_uri) {
webkit_web_view_load_uri(view, "");
toggle_bar(notebook, _SEARCH);
return;
}
bool has_direct_uri_prefix = g_str_has_prefix(uri, "http://") || g_str_has_prefix(uri, "https://") || g_str_has_prefix(uri, "file://") || g_str_has_prefix(uri, "about:");
if (has_direct_uri_prefix){
webkit_web_view_load_uri(view, uri);
return;
}
bool has_common_domain_extension = (strstr(uri, ".com") || strstr(uri, ".org"));
if (has_common_domain_extension){
char tmp[strlen("https://") + strlen(uri) + 1];
snprintf(tmp, sizeof(tmp) + 1, "https://%s", uri);
webkit_web_view_load_uri(view, tmp);
return;
}
int l = SHORTCUT_N + strlen(uri) + 1;
char uri_expanded[l];
str_init(uri_expanded, l);
int check = shortcut_expand(uri, uri_expanded);
bool has_shortcut = (check == 2);
if (has_shortcut){
webkit_web_view_load_uri(view, uri_expanded);
return;
}
char tmp[strlen(uri) + strlen(SEARCH)];
snprintf(tmp, sizeof(tmp), SEARCH, uri);
webkit_web_view_load_uri(view, tmp);
}
/* Deal with new load or changed load */
void redirect_if_annoying(WebKitWebView* view, const char* uri)
{
if (LIBRE_REDIRECT_ENABLED) {
int l = LIBRE_N + strlen(uri) + 1;
char uri_filtered[l];
str_init(uri_filtered, l);
int check = libre_redirect(uri, uri_filtered);
if (check == 2) webkit_web_view_load_uri(view, uri_filtered);
}
}
void set_custom_style(WebKitWebView* view)
{
if (custom_style_enabled) {
char* style_js = malloc(STYLE_N + 1);
read_style_js(style_js);
webkit_web_view_evaluate_javascript(view, style_js, -1, NULL, "rosenrot-style-plugin", NULL, NULL, NULL);
free(style_js);
}
}
void handle_signal_load_changed(WebKitWebView* self, WebKitLoadEvent load_event,
GtkNotebook* notebook)
{
switch (load_event) {
// https://webkitgtk.org/reference/webkit2gtk/2.5.1/WebKitWebView.html
case WEBKIT_LOAD_STARTED:
case WEBKIT_LOAD_COMMITTED:
set_custom_style(self);
case WEBKIT_LOAD_REDIRECTED:
redirect_if_annoying(self, webkit_web_view_get_uri(self));
break;
case WEBKIT_LOAD_FINISHED: {
set_custom_style(self);
/* Add gtk tab title */
const char* webpage_title = webkit_web_view_get_title(self);
const int max_length = 25;
char tab_title[max_length + 1];
if (webpage_title != NULL) {
for (int i = 0; i < (max_length); i++) {
tab_title[i] = webpage_title[i];
if (webpage_title[i] == '\0') {
break;
}
}
tab_title[max_length] = '\0';
}
gtk_notebook_set_tab_label_text(notebook, GTK_WIDGET(self),
webpage_title == NULL ? "" : tab_title);
}
}
}
/* New tabs */
WebKitWebView* create_new_webview()
{
WebKitSettings* settings = webkit_settings_new_with_settings(WEBKIT_DEFAULT_SETTINGS, NULL);
if (CUSTOM_USER_AGENT) {
webkit_settings_set_user_agent(
settings,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
"like Gecko) Chrome/120.0.0.0 Safari/537.3");
// https://www.useragents.me
}
WebKitNetworkSession* network_session = webkit_network_session_new(DATA_DIR, DATA_DIR);
WebKitUserContentManager* contentmanager = webkit_user_content_manager_new();
WebKitCookieManager* cookiemanager = webkit_network_session_get_cookie_manager(network_session);
webkit_cookie_manager_set_persistent_storage(cookiemanager, DATA_DIR "/cookies.sqlite", WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
webkit_cookie_manager_set_accept_policy(cookiemanager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
WebKitWebView* view = g_object_new(WEBKIT_TYPE_WEB_VIEW, "settings", settings, "network-session", network_session, "user-content-manager", contentmanager, NULL);
NULLCHECK(view);
GtkEventController* event_controller = gtk_event_controller_key_new();
g_signal_connect(event_controller, "key-pressed", G_CALLBACK(handle_signal_keypress), NULL);
gtk_widget_add_controller(GTK_WIDGET(view), event_controller);
return view;
}
GtkWidget* handle_signal_create_new_tab(WebKitWebView* self,
WebKitNavigationAction* navigation_action,
GtkNotebook* notebook)
{
NULLCHECK(self);
NULLCHECK(notebook);
if (num_tabs < MAX_NUM_TABS || num_tabs == 0) {
WebKitURIRequest* uri_request = webkit_navigation_action_get_request(navigation_action);
const char* uri = webkit_uri_request_get_uri(uri_request);
webkit_web_view_stop_loading(self);
printf("Creating new window: %s\n", uri);
notebook_create_new_tab(notebook, uri);
gtk_notebook_set_show_tabs(notebook, true);
} else {
webkit_web_view_evaluate_javascript(self, "alert('Too many tabs, not opening a new one')", -1, NULL, "rosenrot-alert-numtabs", NULL, NULL, NULL);
}
return ABORT_REQUEST_ON_CURRENT_TAB;
// Could also return GTK_WIDGET(self), in which case the new uri would also be loaded in the current webview. This could be interesting if I wanted to e.g., open an alternative frontend in a new tab
}
void notebook_create_new_tab(GtkNotebook* notebook, const char* uri)
{
if (num_tabs < MAX_NUM_TABS || MAX_NUM_TABS == 0) {
WebKitWebView* view = create_new_webview();
NULLCHECK(view);
g_signal_connect(view, "load_changed", G_CALLBACK(handle_signal_load_changed), notebook);
g_signal_connect(view, "create", G_CALLBACK(handle_signal_create_new_tab), notebook);
int n = gtk_notebook_append_page(notebook, GTK_WIDGET(view), NULL);
gtk_notebook_set_tab_reorderable(notebook, GTK_WIDGET(view), true);
NULLCHECK(window);
NULLCHECK(bar.widget);
gtk_widget_set_visible(GTK_WIDGET(window), 1);
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
load_uri(view, (uri) ? uri : HOME);
set_custom_style(view);
gtk_notebook_set_current_page(notebook, n);
gtk_notebook_set_tab_label_text(notebook, GTK_WIDGET(view), "-");
webkit_web_view_set_zoom_level(view, ZOOM_START_LEVEL);
num_tabs += 1;
} else {
webkit_web_view_evaluate_javascript(notebook_get_webview(notebook), "alert('Too many tabs, not opening a new one')",
-1, NULL, "rosenrot-alert-numtabs", NULL, NULL, NULL);
}
}
/* Top bar */
void toggle_bar(GtkNotebook* notebook, Bar_entry_mode mode)
{
bar.entry_mode = mode;
switch (bar.entry_mode) {
case _SEARCH: {
const char* url = webkit_web_view_get_uri(notebook_get_webview(notebook));
gtk_entry_set_placeholder_text(bar.line, "Search");
gtk_entry_buffer_set_text(bar.line_text, url, strlen(url));
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 1);
gtk_window_set_focus(window, GTK_WIDGET(bar.line));
break;
}
case _FIND: {
const char* search_text = webkit_find_controller_get_search_text(
webkit_web_view_get_find_controller(notebook_get_webview(notebook)));
if (search_text != NULL)
gtk_entry_buffer_set_text(bar.line_text, search_text, strlen(search_text));
gtk_entry_set_placeholder_text(bar.line, "Find");
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 1);
gtk_window_set_focus(window, GTK_WIDGET(bar.line));
break;
}
case _FILTER: {
gtk_entry_set_placeholder_text(bar.line, "Filter");
gtk_entry_buffer_set_text(bar.line_text, "", strlen(""));
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 1);
gtk_window_set_focus(window, GTK_WIDGET(bar.line));
break;
}
case _HIDDEN:
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
}
}
// Handle what happens when the user is on the bar and presses enter
void handle_signal_bar_press_enter(GtkEntry* self, GtkNotebook* notebook) /* consider passing notebook as the data here? */
{
WebKitWebView* view = notebook_get_webview(notebook);
const char* bar_line_text = gtk_entry_buffer_get_text(bar.line_text);
switch (bar.entry_mode) {
case _SEARCH: {
load_uri(view, bar_line_text);
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
break;
}
case _FIND: {
webkit_find_controller_search(
webkit_web_view_get_find_controller(view),
bar_line_text,
WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | WEBKIT_FIND_OPTIONS_WRAP_AROUND,
G_MAXUINT);
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
break;
}
case _FILTER: {
const char* js_template = "filterByKeyword(\"%s\")";
char js_command[strlen(js_template) + strlen(bar_line_text) + 2];
snprintf(js_command, sizeof(js_command) + 1, js_template, bar_line_text);
webkit_web_view_evaluate_javascript(view, js_command, -1, NULL, "rosenrot-filter-plugin", NULL, NULL, NULL);
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
break;
}
case _HIDDEN:
// no op
}
}
/* Shortcuts */
int handle_shortcut(func id)
{
static double zoom = ZOOM_START_LEVEL;
static bool is_fullscreen = 0;
WebKitWebView* view = notebook_get_webview(notebook);
NULLCHECK(notebook);
NULLCHECK(view);
switch (id) {
case goback:
webkit_web_view_go_back(view);
break;
case goforward:
webkit_web_view_go_forward(view);
break;
case toggle_custom_style:
custom_style_enabled ^= 1;
// fallthrough
case refresh:
webkit_web_view_reload(view);
break;
case refresh_force:
webkit_web_view_reload_bypass_cache(view);
break;
case back_to_home:
load_uri(view, HOME);
break;
case zoomin:
webkit_web_view_set_zoom_level(view,
(zoom += ZOOM_STEPSIZE));
break;
case zoomout:
webkit_web_view_set_zoom_level(view,
(zoom -= ZOOM_STEPSIZE));
break;
case zoom_reset:
webkit_web_view_set_zoom_level(view,
(zoom = ZOOM_START_LEVEL));
break;
case prev_tab:; // declarations aren't statements
// https://stackoverflow.com/questions/92396/why-cant-variables-be-declared-in-a-switch-statement
int n = gtk_notebook_get_n_pages(notebook);
int k = gtk_notebook_get_current_page(notebook);
int o = (n + k - 1) % n;
gtk_notebook_set_current_page(notebook, o);
break;
case next_tab:;
int m = gtk_notebook_get_n_pages(notebook);
int l = gtk_notebook_get_current_page(notebook);
int p = (l + 1) % m;
gtk_notebook_set_current_page(notebook, p);
break;
case close_tab:
num_tabs -= 1;
switch(num_tabs){
case 0:
exit(0);
break;
case 1:
gtk_notebook_set_show_tabs(notebook, false);
// fallthrough
default:
gtk_notebook_remove_page(notebook, gtk_notebook_get_current_page(notebook));
}
break;
case toggle_fullscreen:
if (is_fullscreen)
gtk_window_unfullscreen(window);
else
gtk_window_fullscreen(window);
is_fullscreen = !is_fullscreen;
break;
case show_searchbar:
toggle_bar(notebook, _SEARCH);
break;
case show_finder:
toggle_bar(notebook, _FIND);
break;
case filter:
toggle_bar(notebook, _FILTER);
break;
case finder_next:
webkit_find_controller_search_next(webkit_web_view_get_find_controller(view));
break;
case finder_prev:
webkit_find_controller_search_previous(webkit_web_view_get_find_controller(view));
break;
case new_tab:
notebook_create_new_tab(notebook, NULL);
gtk_notebook_set_show_tabs(notebook, true);
toggle_bar(notebook, _SEARCH);
break;
case hide_bar:
gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
toggle_bar(notebook, _HIDDEN);
break;
case halve_window:
gtk_window_set_default_size(window, FULL_WIDTH/2, HEIGHT);
break;
case rebig_window:
gtk_window_set_default_size(window, FULL_WIDTH, HEIGHT);
break;
case prettify: {
if (READABILITY_ENABLED) {
char* readability_js = malloc(READABILITY_N + 1);
read_readability_js(readability_js);
webkit_web_view_evaluate_javascript(view, readability_js, -1, NULL, "rosenrot-readability-plugin", NULL, NULL, NULL);
free(readability_js);
}
break;
}
case save_uri_to_txt: {
const char* uri = webkit_web_view_get_uri(view);
FILE *f = fopen("/opt/rosenrot/uris.txt", "a");
if (f == NULL) {
printf("Error opening /opt/rosenrot/uris.txt");
} else {
fprintf(f, "%s\n", uri);
fclose(f);
webkit_web_view_evaluate_javascript(view, "alert('Saved current uri to /opt/rosenrot/uris.txt')", -1, NULL, "rosenrot-alert-numtabs", NULL, NULL, NULL);
}
break;
}
case open_uri_in_brave: {
const char* uri = webkit_web_view_get_uri(view);
const char* brave_command = "brave-browser --app=%s --new-window --start-fullscreen &";
char cmd[strlen(brave_command) + strlen(uri) + 2];
snprintf(cmd, sizeof(cmd) + 1, brave_command, uri);
system(cmd);
break;
}
}
return 1;
}
/* Listen to keypresses */
static int handle_signal_keypress(void* self, int keyval, int keycode,
GdkModifierType state, void* controller)
{
if (0) {
printf("New keypress\n");
printf("Keypress state: %d\n", state);
printf("Keypress value: %d\n", keyval);
}
for (int i = 0; i < sizeof(shortcut) / sizeof(shortcut[0]); i++) {
if ((state & shortcut[i].mod || shortcut[i].mod == 0x0) && keyval == shortcut[i].key) {
printf("New shortcut, with id: %d\n", shortcut[i].id);
return handle_shortcut(shortcut[i].id);
}
}
return 0;
}
int main(int argc, char** argv)
{
// Initialize GTK in general
gtk_init();
g_object_set(gtk_settings_get_default(), GTK_SETTINGS_CONFIG_H, NULL);
// https://docs.gtk.org/gobject/method.Object.set.html
GtkCssProvider* css = gtk_css_provider_new();
gtk_css_provider_load_from_path(css, "/opt/rosenrot/style-gtk4.css");
gtk_style_context_add_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_USER);
// Create the main window
window = GTK_WINDOW(gtk_window_new());
gtk_window_set_default_size(window, WIDTH, HEIGHT);
// Set up notebook
notebook = GTK_NOTEBOOK(gtk_notebook_new());
gtk_notebook_set_show_tabs(notebook, false);
gtk_notebook_set_show_border(notebook, false);
gtk_window_set_child(window, GTK_WIDGET(notebook));
// Set up top bar
bar.line_text = GTK_ENTRY_BUFFER(gtk_entry_buffer_new("", 0));
bar.line = GTK_ENTRY(gtk_entry_new_with_buffer(bar.line_text));
gtk_entry_set_alignment(bar.line, 0.5);
gtk_widget_set_size_request(GTK_WIDGET(bar.line), BAR_WIDTH, -1);
bar.widget = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_header_bar_set_title_widget(bar.widget, GTK_WIDGET(bar.line));
gtk_window_set_titlebar(window, GTK_WIDGET(bar.widget));
// Setup signals
GtkEventController* event_controller = gtk_event_controller_key_new();
g_signal_connect(event_controller, "key-pressed", G_CALLBACK(handle_signal_keypress), NULL);
gtk_widget_add_controller(GTK_WIDGET(window), event_controller);
g_signal_connect(bar.line, "activate", G_CALLBACK(handle_signal_bar_press_enter), notebook);
g_signal_connect(GTK_WIDGET(window), "destroy", G_CALLBACK(exit), notebook);
// Load first tab
char* first_uri = argc > 1 ? argv[1] : HOME;
notebook_create_new_tab(notebook, first_uri);
// Show to user
// The first two commands are redundant with notebook_create_new_tab
gtk_window_present(window);
gtk_widget_set_visible(GTK_WIDGET(window), 1);
if (argc != 0) gtk_widget_set_visible(GTK_WIDGET(bar.widget), 0);
// Deal with more tabs, if any
if (argc > 2) {
gtk_notebook_set_show_tabs(notebook, true);
for (int i = 2; i < argc; i++) {
notebook_create_new_tab(notebook, argv[i]);
}
}
// Enter the main event loop, and wait for user interaction
while (g_list_model_get_n_items(gtk_window_get_toplevels()) > 0 && num_tabs > 0)
g_main_context_iteration(NULL, TRUE);
return 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

View File

@ -1,78 +0,0 @@
#!/bin/sh
# FIXME: Sometimes when new bookmark added cant open it
BOOKMARKS_PATH=~/.cache/rose/bookmarks
search()
{
[ -f $BOOKMARKS_PATH ] || touch $BOOKMARKS_PATH
BOOKMARKS=$(cat ~/.cache/rose/bookmarks)
CURRENT_ID=$(xdotool getactivewindow)
SEARCH=$(printf "Add\nRemove\n$(printf "$BOOKMARKS" | cut -f1 -d" ")" | dmenu -p SEARCH:)
[ "$SEARCH" = Add ] && {
printf "" | dmenu -p Url: | sed 's/\./ /g' >> $BOOKMARKS_PATH
$0 & exit
}
[ "$SEARCH" = Remove ] && {
printf "$(grep -v "$(printf "$BOOKMARKS" | dmenu -p Select:)" $BOOKMARKS_PATH)" > $BOOKMARKS_PATH
$0 & exit
}
[ -z "$SEARCH" ] && exit
ROSE_GO=$(xprop -id "$CURRENT_ID" | grep rose)
[ -z "$1" ] || ROSE_GO=''
[ -z "$ROSE_GO" ] || {
[ -z "$(printf "$BOOKMARKS" | grep -w "$SEARCH")" ] && {
xprop -id "$CURRENT_ID" -f "_ROSE_GO" 8u -set "_ROSE_GO" https://duckduckgo.com/?q="$SEARCH"
exit
} || {
FULL_URL=$(printf "$BOOKMARKS" | grep -w "$SEARCH" | sed 's/ /\./g')
xprop -id "$CURRENT_ID" -f "_ROSE_GO" 8u -set "_ROSE_GO" https://$FULL_URL
exit
}
}
[ -z "$(printf "$BOOKMARKS" | grep -w "$SEARCH")" ] && {
rose https://duckduckgo.com/?q="$SEARCH"
} || {
FULL_URL="$(printf "$BOOKMARKS" | grep "$SEARCH" | sed 's/ /\./g')"
rose https://"$FULL_URL"
}
}
find()
{
FIND=$(printf "" | dmenu -p Find:)
CURRENT_ID=$(xdotool getactivewindow)
ROSE_FIND=$(xprop -id "$CURRENT_ID" | grep rose)
[ -z "$ROSE_FIND" ] || xprop -id "$CURRENT_ID" -f "_ROSE_FIND" 8u -set "_ROSE_FIND" "$FIND"
}
history()
{
CHOOSE=$(tac ~/.cache/rose/history | dmenu -p History:);
CURRENT_ID=$(xdotool getactivewindow)
ROSE_GO=$(xprop -id "$CURRENT_ID" | grep rose)
[ -z "$ROSE_GO" ] || xprop -id "$CURRENT_ID" -f "_ROSE_GO" 8u -set "_ROSE_GO" "$CHOOSE"
}
[ -z "$1" ] && search || {
INSTALL_PATH=/usr/local/bin/dmenu_rose
case "$1" in
install) (set -x; cp $0 $INSTALL_PATH);;
uninstall) (set -x; rm $INSTALL_PATH);;
add_bookmark) echo "$2" >> $BOOKMARKS_PATH;;
new_window) search new;;
find) find;;
history) history;
esac
}

41
styles-gtk/style-gtk3.css Normal file
View File

@ -0,0 +1,41 @@
* {
font-size: 25px;
color: #333;
}
/* 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 {
padding: 3px;
}
tab {
margin: 2px 5px 4px 0px; /* top right bottom left */
padding: 5px;
border-style: solid;
font-size: 27px;
}
entry {
padding-left: 10px;
}
entry:focus {
padding-left: 10px;
}

41
styles-gtk/style-gtk4.css Normal file
View File

@ -0,0 +1,41 @@
* {
font-size: 25px;
color: #333;
}
/* Make titlebar pretty gigantic. I'm pretty myopic. */
.titlebar {
padding: 5px;
font-size: 30px;
}
.titlebar * {
padding: 5px;
font-size: 27px;
}
header * {
font-size: 20px;
padding: 5px;
}
tabs {
padding: 3px;
}
tab {
margin: 2px 5px 4px 0px; /* top right bottom left */
padding: 5px;
border-style: solid;
font-size: 27px;
}
entry {
padding-left: 10px;
}
entry:focus {
padding-left: 10px;
}

View File

@ -1,8 +1,9 @@
-I/usr/include/gtk-4.0 -I/usr/include/webkitgtk-4.0
-I/usr/include/pango-1.0
-I/usr/include/glib-2.0 -I/usr/include/glib-2.0
-I/usr/lib/glib-2.0/include -I/usr/lib/glib-2.0/include
-I/usr/include/sysprof-4 -I/usr/include/sysprof-4
-I/usr/include/gtk-3.0
-I/usr/include/pango-1.0
-I/usr/include/harfbuzz -I/usr/include/harfbuzz
-I/usr/include/freetype2 -I/usr/include/freetype2
-I/usr/include/libpng16 -I/usr/include/libpng16
@ -13,12 +14,14 @@
-I/usr/include/lzo -I/usr/include/lzo
-I/usr/include/pixman-1 -I/usr/include/pixman-1
-I/usr/include/gdk-pixbuf-2.0 -I/usr/include/gdk-pixbuf-2.0
-I/usr/include/graphene-1.0
-I/usr/lib/graphene-1.0/include
-I/usr/include/gio-unix-2.0 -I/usr/include/gio-unix-2.0
-I/usr/include/webkitgtk-5.0 -I/usr/include/cloudproviders
-mfpmath=sse -I/usr/include/atk-1.0
-msse -I/usr/include/at-spi2-atk/2.0
-msse2 -I/usr/include/at-spi-2.0
-I/usr/include/libsoup-3.0 -I/usr/include/dbus-1.0
-I/usr/lib/dbus-1.0/include
-I/usr/include/libsoup-2.4
-I/usr/include/libxml2
-pthread -pthread

View File

@ -0,0 +1,5 @@
DEPS='webkit2gtk-4.0'
INCS=`pkg-config --cflags ${DEPS}`
LIBS=`pkg-config --libs ${DEPS}`
echo $INCS
echo $LIBS

View File

@ -0,0 +1,42 @@
# Key dependencies
sudo apt install git vim gcc make
sudo apt install libwebkit2gtk-4.1-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 build3 # or just make
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"

View File

@ -0,0 +1,42 @@
# Key dependencies
sudo apt install libwebkitgtk-6.0-dev
sudo apt install libgtk-4-dev
# Optional adblock
git clone https://github.com/jun7/wyebadblock
cd wyebadblock
sudo apt install gstreamer1.0-plugins-good gstreamer1.0-libav
WEBKITVER=4.1 make
sudo WEBKITVER=4.1 make install
cd ..
mkdir -p ~/.config/wyebadblock
cd ~/.config/wyebadblock
wget https://easylist.to/easylist/easylist.txt
cd -
cd ../..
make build4
sudo make install
cd -
# Debian desktop icon
chmod +x rosenrot.desktop
sudo cp rosenrot.desktop /usr/share/applications
mkdir -p /opt/rosenrot
sudo cp rosenrot-desktop-icon.png /opt/rosenrot/
# Optionally, set a shortcut (within GNOME)
# https://askubuntu.com/questions/597395/how-to-set-custom-keyboard-shortcuts-from-terminal/1007035#1007035
name="rose"
binding="<CTRL><SHIFT>W"
action="/usr/bin/rosenrot"
media_keys=org.gnome.settings-daemon.plugins.media-keys
custom_kbd=org.gnome.settings-daemon.plugins.media-keys.custom-keybinding
kbd_path=/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/$name/
new_bindings=`gsettings get $media_keys custom-keybindings | sed -e"s>'\]>','$kbd_path']>"| sed -e"s>@as \[\]>['$kbd_path']>"`
gsettings set $media_keys custom-keybindings "$new_bindings"
gsettings set $custom_kbd:$kbd_path name "$name"
gsettings set $custom_kbd:$kbd_path binding "$binding"
gsettings set $custom_kbd:$kbd_path command "$action"

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 KiB

View File

@ -0,0 +1,9 @@
#!/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

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

View File

@ -0,0 +1,37 @@
@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;
}

View File

@ -0,0 +1,52 @@
@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: 16px;
/*@Base; */
/* border-bottom-color: @Base; */
}
window, notebook, headerbar {
background: @Base;
}
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;
/*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;
}

View File

@ -0,0 +1,70 @@
@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;
}

View File

@ -0,0 +1 @@
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.

View File

@ -0,0 +1,24 @@
# 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

View File

@ -0,0 +1,9 @@
#!/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

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

View File

@ -0,0 +1 @@
<https://www.onlygfx.com/red-rose-png-image-transparent/>

View File

@ -0,0 +1,9 @@
#!/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

View File

@ -0,0 +1,3 @@
#!/bin/sh
GIO_MODULE_DIR=/usr/lib/x86_64-linux-gnu/gio/modules/ /bin/rose "$1"

View File

@ -0,0 +1,9 @@
In case you arrive at a segmentation fault when working on rose, you can use valgrind.
To do this, you can compile rose with the `DEBUG` value in `build.sh` set to `-g`
and then:
```
valgrind --track-origins=yes ./rose
```

350
window.c
View File

@ -1,350 +0,0 @@
#include "window.h"
#define LENGTH(x) ((int) (sizeof(x) / sizeof(x[0])))
struct _RoseWindow {
GtkApplicationWindow parent_instance;
guint xid;
GtkWindow *window;
GtkApplication *app;
WebKitWebView *webview[9];
WebKitWebInspector *inspector;
WebKitFindOptions *findopts;
GHashTable *action_groups;
GHashTable *action_labels;
};
enum {
PROP_ACTIVE_CHILD
};
static float zoom = 1.0;
static int tabid;
G_DEFINE_TYPE(RoseWindow, rose_window, GTK_TYPE_APPLICATION_WINDOW)
static void read_clipboard(GObject *object,
GAsyncResult *res,
gpointer webview)
{
GdkClipboard *clipboard = GDK_CLIPBOARD(object);
webkit_web_view_load_uri(
WEBKIT_WEB_VIEW(webview),
gdk_clipboard_read_text_finish(clipboard, res, NULL)
);
}
static void new_tab(RoseWindow *window, unsigned id)
{
/* window->webview[id] = WEBKIT_WEB_VIEW(rose_webview_new()); */
/* if (!options[HOMEPAGE]) */
/* options[HOMEPAGE] = "https://duckduckgo.com"; */
/* */
/* rose_webview_load_url(WEBKIT_WEB_VIEW(window->webview[id]), */
/* options[HOMEPAGE]); */
/* gtk_window_set_child( */
/* GTK_WINDOW( */
/* gtk_widget_get_root( */
/* GTK_WIDGET( */
/* window->webview[tabid++] */
/* ) */
/* ) */
/* ), */
/* GTK_WIDGET(window->webview[id]) */
/* ); */
/* gtk_widget_show(GTK_WIDGET(window->webview[id])); */
}
static void goto_tab(RoseWindow *window, unsigned id)
{
/* if (window->webview[id]) { */
/* puts("new tab"); */
/* new_tab(window, id); */
/* return; */
/* } */
/* printf("%u -> %u\n", tabid, id); */
int len = LENGTH(window->webview);
while (len--)
printf("%i", WEBKIT_IS_WEB_VIEW(window->webview[len]));
puts("");
/* new_tab(window, id); */
/* gtk_window_set_child( */
/* GTK_WINDOW( */
/* gtk_widget_get_root( */
/* GTK_WIDGET( */
/* window->webview[id] */
/* ) */
/* ) */
/* ), */
/* GTK_WIDGET(window->webview[id]) */
/* ); */
/* */
/* gtk_widget_hide(GTK_WIDGET(window->webview[tabid])); */
/* gtk_widget_show(GTK_WIDGET(window->webview[id])); */
}
static gboolean key_press_callback(RoseWindow *window,
guint keyval,
guint keycode,
GdkModifierType state)
{
(void) keycode;
for (int i = 0; i < LENGTH(keys); i++) {
if (keys[i].modkey == state
&& keys[i].keycod == keyval) {
switch (keys[i].funcid) {
case goback:
webkit_web_view_go_back(window->webview[tabid]);
break;
case goforward:
webkit_web_view_go_forward(window->webview[tabid]);
break;
case copy_url: {
GdkDisplay *dpy = gdk_display_get_default();
gdk_clipboard_set_text(
gdk_display_get_clipboard(dpy),
webkit_web_view_get_uri(window->webview[tabid])
);
} break;
case paste_url: {
GdkDisplay *dpy = gdk_display_get_default();
GdkClipboard *clipboard = gdk_display_get_clipboard(dpy);
gdk_clipboard_read_text_async(clipboard, NULL, read_clipboard, window->webview[tabid]);
} break;
case fullscreen:
if (gtk_window_is_fullscreen(GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(window->webview[tabid])))))
gtk_window_unfullscreen(GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(window->webview[tabid]))));
else
gtk_window_fullscreen(GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(window->webview[tabid]))));
break;
case search: {
int id = fork();
if (id == 0) {
if (glob_dpy)
close(ConnectionNumber(glob_dpy));
setsid();
char* argument_list[] = { "/bin/sh", "-c", "dmenu_rose", NULL};
execvp("/bin/sh", argument_list);
perror(" failed");
exit(1);
} else {
wait(&id);
char *uri;
if (strcmp((uri = (char*)getatom(AtomGo)), ""))
webkit_web_view_load_uri(window->webview[tabid], uri);
}
} break;
case find: {
int id = fork();
if (id == 0) {
if (glob_dpy)
close(ConnectionNumber(glob_dpy));
setsid();
char* argument_list[] = { "/bin/sh", "-c", "dmenu_rose\tfind", NULL};
execvp("/bin/sh", argument_list);
perror(" failed");
exit(1);
} else {
wait(&id);
WebKitFindController *finder = webkit_web_view_get_find_controller(window->webview[tabid]);
webkit_find_controller_search(
finder,
getatom(AtomFind),
WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE |
WEBKIT_FIND_OPTIONS_WRAP_AROUND, G_MAXUINT);
}
} break;
case findnext: {
WebKitFindController *finder = webkit_web_view_get_find_controller(window->webview[tabid]);
webkit_find_controller_search_next(finder);
} break;
case findprev: {
WebKitFindController *finder = webkit_web_view_get_find_controller(window->webview[tabid]);
webkit_find_controller_search_previous(finder);
} break;
case zoomin:
zoom += 0.1;
webkit_web_view_set_zoom_level(window->webview[tabid], zoom);
break;
case zoomout:
zoom -= 0.1;
webkit_web_view_set_zoom_level(window->webview[tabid], zoom);
break;
case zoomreset:
zoom = 1;
webkit_web_view_set_zoom_level(window->webview[tabid], zoom);
break;
case inspector:
window->inspector = webkit_web_view_get_inspector(window->webview[tabid]);
if (webkit_web_inspector_is_attached(window->inspector))
webkit_web_inspector_close(window->inspector);
else
webkit_web_inspector_show(window->inspector);
return GDK_EVENT_STOP;
break;
case up:
webkit_web_view_run_javascript(window->webview[tabid], "window.scrollBy(0,-100);", NULL, NULL, NULL);
break;
case down:
webkit_web_view_run_javascript(window->webview[tabid], "window.scrollBy(0,100);", NULL, NULL, NULL);
break;
case reload:
webkit_web_view_reload(window->webview[tabid]);
break;
case reloadforce:
webkit_web_view_reload_bypass_cache(window->webview[tabid]);
break;
case history: {
int id = fork();
if (id == 0) {
setsid();
char* argument_list[] = { "/bin/sh", "-c", "dmenu_rose\thistory", NULL};
execvp("/bin/sh", argument_list);
perror(" failed");
exit(1);
} else {
wait(&id);
char *uri;
if (strcmp((uri = (char*)getatom(AtomGo)), ""))
webkit_web_view_load_uri(window->webview[tabid], uri);
}
} break;
case gotop: {
webkit_web_view_run_javascript(window->webview[tabid], "window.scrollTo(0,0);", NULL, NULL, NULL);
} break;
case gobottom: {
webkit_web_view_run_javascript(
window->webview[tabid],
"window.scrollTo(0, document.body.scrollHeight);",
NULL, NULL, NULL);
} break;
case tabnext:
if (tabid != 8)
goto_tab(window, ++tabid);
break;
case tabprev:
if (tabid != 0)
goto_tab(window, --tabid);
break;
}
}
}
return GDK_EVENT_PROPAGATE;
}
static void rose_window_constructed(GObject *object)
{
G_OBJECT_CLASS(rose_window_parent_class)->constructed(object);
}
static void rose_window_init(RoseWindow *window)
{
(void) window;
}
static void destroy()
{
exit(0);
}
guint rose_window_show(GtkApplication *app, RoseWindow *window, const char *url)
{
tabid = 0;
memset(window, 0, sizeof(*window));
window->app = app;
window->window = GTK_WINDOW(gtk_application_window_new(window->app));
/* gtk_application_set_menubar(window->app, FALSE); */
window->webview[0] = WEBKIT_WEB_VIEW(webkit_web_view_new());
g_signal_connect(G_OBJECT(window->window), "destroy",
G_CALLBACK(destroy), NULL);
g_signal_connect(G_OBJECT(window->webview[tabid]), "web-process-terminated",
G_CALLBACK(destroy), NULL);
/* g_signal_connect(G_OBJECT(window->webview[tabid]), "load-changed", */
/* G_CALLBACK(rose_load_changed_callback), window->webview[0]); */
if (url)
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(window->webview[tabid]), url);
if (!(appearance[WIDTH] && appearance[HEIGHT])) {
appearance[WIDTH] = 1280;
appearance[HEIGHT] = 720;
}
puts("");
gtk_window_set_default_size(GTK_WINDOW(window->window), appearance[WIDTH], appearance[HEIGHT]);
gtk_window_set_child(GTK_WINDOW(window->window), GTK_WIDGET(window->webview[tabid]));
GtkEventController *controller;
controller = gtk_event_controller_key_new();
g_signal_connect_swapped(controller, "key-pressed", G_CALLBACK(key_press_callback), window);
gtk_widget_add_controller(GTK_WIDGET(window->window), controller);
gtk_widget_show(GTK_WIDGET(window->window));
gtk_widget_show(GTK_WIDGET(window->webview[0]));
return 12;
}
void rose_window_set_webview(RoseWindow *window, GtkWidget *webview)
{
/* window->webview[tabid] = WEBKIT_WEB_VIEW(webview); */
}
static void rose_window_class_init(RoseWindowClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS(class);
object_class->constructed = rose_window_constructed;
}
RoseWindow* rose_window_new(GtkApplication *app)
{
return g_object_new(
GTK_TYPE_APPLICATION_WINDOW,
"application", GTK_APPLICATION(app),
NULL
);
}

View File

@ -1,19 +0,0 @@
#pragma once
#include "rose.h"
#include <gtk/gtk.h>
#include <gdk/x11/gdkx.h>
#define ROSE_TYPE_WINDOW rose_window_get_type()
G_DECLARE_FINAL_TYPE(RoseWindow, rose_window, ROSE, WINDOW, GtkApplicationWindow)
RoseWindow* rose_window_new();
guint rose_window_show(GtkApplication *app, RoseWindow *window, const char *url);
gboolean rose_window_close(RoseWindow *window);
void rose_window_set_webview(RoseWindow *window, GtkWidget *webview);
GActionGroup* rose_window_get_action_group(RoseWindow *window,
const char *prefix);