├── .clang-format ├── .gitignore ├── config.mk ├── license ├── makefile ├── readme.md ├── src ├── client.c ├── client.h ├── debug.c ├── debug.h ├── keymap.c ├── keymap.h ├── lua.c ├── lua.h ├── path.c ├── path.h ├── rose.c ├── search.c ├── search.h ├── split.c ├── split.h ├── tab.c ├── tab.h ├── webview.c ├── webview.h ├── window.c └── window.h └── todo.md /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | IncludeBlocks: Regroup 4 | UseTab: ForContinuationAndIndentation 5 | AllowShortIfStatementsOnASingleLine: false 6 | IndentWidth: 4 7 | TabWidth: 4 8 | IndentCaseLabels: false 9 | AllowShortFunctionsOnASingleLine: Empty 10 | BreakBeforeBinaryOperators: NonAssignment 11 | BreakBeforeBraces: Custom 12 | BraceWrapping: 13 | AfterEnum: true 14 | AfterStruct: true 15 | AfterFunction: true 16 | AfterUnion: true 17 | SplitEmptyFunction: false 18 | SplitEmptyRecord: false 19 | BeforeCatch: false 20 | BeforeElse: false 21 | BeforeWhile: false 22 | SpaceAfterCStyleCast: true 23 | PointerAlignment: Right 24 | ReferenceAlignment: Left 25 | AlignOperands: AlignAfterOperator 26 | AlignConsecutiveMacros: true 27 | ColumnLimit: 80 28 | SpaceBeforeSquareBrackets: false 29 | SpaceBeforeAssignmentOperators: true 30 | SpaceBeforeParens: Custom 31 | SpaceBeforeParensOptions: 32 | AfterControlStatements: true 33 | AfterFunctionDefinitionName: false 34 | AfterOverloadedOperator: false 35 | SpacesInCStyleCastParentheses: false 36 | BitFieldColonSpacing: Both 37 | EmptyLineAfterAccessModifier: Never 38 | EmptyLineBeforeAccessModifier: LogicalBlock 39 | IndentGotoLabels: false 40 | IndentPPDirectives: AfterHash 41 | PPIndentWidth: 1 42 | SpaceInEmptyBlock: true 43 | SeparateDefinitionBlocks: Leave 44 | ... 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | compile_flags.txt 3 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | VERSION := 3.0 2 | 3 | # Programs 4 | CC ?= cc 5 | LD := $(if $(shell which mold),mold,ld) 6 | PKGCONFIG := pkg-config 7 | 8 | # Paths 9 | PREFIX := /usr/local 10 | LIBPREFIX := $(PREFIX)/lib 11 | LIBDIR := $(LIBPREFIX)/rose 12 | BINDIR := $(PREFIX)/bin 13 | 14 | # Gtk version (3|4) 15 | # Default is 3 16 | GTK ?= 4 17 | 18 | # Includes and libraries 19 | ifeq ($(GTK), 4) 20 | WEBKIT_INCS := `$(PKGCONFIG) --cflags webkitgtk-6.0 webkitgtk-web-process-extension-6.0` 21 | WEBKIT_LIBS := `$(PKGCONFIG) --libs webkitgtk-6.0 webkitgtk-web-process-extension-6.0` 22 | else ifeq ($(GTK), 3) 23 | WEBKIT_INCS := `$(PKGCONFIG) --cflags webkit2gtk-4.0 webkit2gtk-web-extension-4.0` 24 | WEBKIT_LIBS := `$(PKGCONFIG) --libs webkit2gtk-4.0 webkit2gtk-web-extension-4.0` 25 | endif 26 | 27 | LUA_INCS := `$(PKGCONFIG) --cflags lua` 28 | LUA_LIBS := `$(PKGCONFIG) --libs lua` 29 | 30 | CFLAGS := -Wall -Wextra -Iinclude \ 31 | -march=native -pipe \ 32 | -DVERSION=\"$(VERSION)\" -DGTK=$(GTK) \ 33 | $(WEBKIT_INCS) $(LUA_INCS) -flto 34 | 35 | LDFLAGS := $(WEBKIT_LIBS) $(LUA_LIBS) -fuse-ld=$(LD) 36 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 mini-rose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | -include build/makedeps.mk 2 | include config.mk 3 | 4 | MAKEFLAGS += -j$(nproc) -r -R 5 | SOURCE := $(shell find src -type f -name '*.c') 6 | OBJECT := $(patsubst src/%.c,build/%.o,$(SOURCE)) 7 | SRCDIR := $(shell find src -type d -wholename 'src/*' | sed 's/^src/build/g') 8 | OUTPUT := build/rose 9 | 10 | BUILDTYPE ?= DEBUG 11 | 12 | ifeq ($(BUILDTYPE), DEBUG) 13 | CFLAGS += -O0 -ggdb -DDEBUG=1 14 | else ifeq ($(BUILDTYPE), RELEASE) 15 | CFLAGS += -Ofast 16 | endif 17 | 18 | rose: build build/makedeps.mk $(OUTPUT) 19 | 20 | build: 21 | @mkdir -p build $(SRCDIR) 22 | 23 | build/makedeps.mk: build $(SOURCE) 24 | $(CC) -MM $(CFLAGS) $(SOURCE) | \ 25 | sed 's/\b\([a-zA-Z0-9_]*\.o\)\b/build\/\1/g' > build/makedeps.mk 26 | 27 | run: rose 28 | $(OUTPUT) 29 | 30 | install: rose 31 | cp -f $(OUTPUT) $(BINDIR)/rose 32 | 33 | uninstall: 34 | rm -f $(BINDIR)/mcc 35 | 36 | clean: 37 | rm -rf build 38 | 39 | compile_flags.txt: makefile 40 | echo $(CFLAGS) | tr ' ' '\n' > compile_flags.txt 41 | 42 | $(OUTPUT): $(OBJECT) 43 | @printf " \033[32m LD \033[m $(shell basename $@)\n" 44 | @$(CC) -o $@ $(USE_LD) $(CFLAGS) $(LDFLAGS) $^ 45 | 46 | build/%.o: src/%.c 47 | @printf " \033[33m CC \033[m $(shell basename $@)\n" 48 | @$(CC) -c -o $@ $(CFLAGS) $< 49 | 50 | .PHONY: install uninstall clean 51 | .SILENT: build/makedeps.mk 52 | .DEFAULT_GOAL := rose 53 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
2 |

Rose Browser

3 | 4 |

5 | Small browser built on gtk and webkit.
6 |

7 | 8 | ## 🌌 Showcase 9 | ![image](https://user-images.githubusercontent.com/93622468/236818028-9d90bc83-7bf9-4666-b95a-763a6a1fd3b1.png) 10 | 11 | 12 | ## ⚡️ Requirements 13 | - lua (5.0+) 14 | - webkitgtk (4.0|6.0) 15 | - gtk (3|4) 16 | - libsanitizer-devel 17 | 18 | - Video and audio: 19 | - gst-plugins-bad 20 | - gst-plugins-base 21 | - gst-plugins-good 22 | - gst-libav 23 | - gstreamer-vaapi (gpu acceleration) 24 | 25 | ## ✨ Features 26 | - supports the lastes webkit and gtk features 27 | - configuration via lua modules 28 | 29 | ## 📦 Installation 30 | ```sh 31 | $ git clone https://github.com/mini-rose/rose-browser && cd rose-browser 32 | $ (doas|sudo) GTK=(3|4) BUILDTYPE=RELEASE make install 33 | ``` 34 | 35 | ## 🚀 Usage 36 | See how to configure rose [here](https://github.com/mini-rose/rose-browser/wiki/Configuration). 37 | 38 | ## 📝 TODO 39 | See planned changes [here](https://github.com/mini-rose/rose-browser/blob/main/todo.md). 40 | -------------------------------------------------------------------------------- /src/client.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | #include "lua.h" 3 | #include "split.h" 4 | #include "window.h" 5 | #include "debug.h" 6 | 7 | #include 8 | 9 | typedef struct 10 | { 11 | RoseClient **clients; 12 | int n_clients; 13 | } RoseClients; 14 | 15 | RoseClients *rose_client_get_all(void) 16 | { 17 | static RoseClients clients = { NULL, 0 }; 18 | return &clients; 19 | } 20 | 21 | RoseClient *rose_client_new(void) 22 | { 23 | RoseClients *rcs = rose_client_get_all(); 24 | RoseClient *rc = calloc(sizeof(*rc), 1); 25 | 26 | rc->window = rose_window_get(); 27 | rc->id = rand(); 28 | 29 | while (rose_client_get_by_id(rc->id)) { 30 | rc->id = rand(); 31 | } 32 | 33 | rcs->clients = realloc(rcs->clients, sizeof(RoseClient*) * (rcs->n_clients + 1)); 34 | rcs->clients[rcs->n_clients++] = rc; 35 | 36 | return rc; 37 | } 38 | 39 | RoseClient *rose_client_get_by_window(RoseWindow *window) 40 | { 41 | RoseClients *rcs = rose_client_get_all(); 42 | 43 | for (int i = 0; i < rcs->n_clients; i++) { 44 | if (rcs->clients[i]->window == window) 45 | return rcs->clients[i]; 46 | } 47 | 48 | return NULL; 49 | } 50 | 51 | RoseClient *rose_client_get_by_id(int id) 52 | { 53 | RoseClients *rcs = rose_client_get_all(); 54 | 55 | for (int i = 0; i < rcs->n_clients; i++) { 56 | if (rcs->clients[i]->id == id) 57 | return rcs->clients[i]; 58 | } 59 | 60 | return NULL; 61 | } 62 | 63 | void rose_client_destroy_by_window(RoseWindow *window) 64 | { 65 | RoseClients *rcs = rose_client_get_all(); 66 | 67 | for (int i = 0; i < rcs->n_clients; i++) { 68 | if (rcs->clients[i]->window == window) { 69 | debug("Destroying window (id: %i)", rcs->clients[i]->id); 70 | free(rcs->clients[i]); 71 | rcs->clients[i] = NULL; 72 | 73 | for (int j = i + 1; j < rcs->n_clients; j++) { 74 | rcs->clients[j - 1] = rcs->clients[j]; 75 | } 76 | 77 | rcs->n_clients--; 78 | rcs->clients = realloc(rcs->clients, rcs->n_clients * sizeof(RoseClient*)); 79 | 80 | if (rcs->n_clients == 0) 81 | exit(0); 82 | } 83 | } 84 | } 85 | 86 | void rose_client_destroy_by_id(int id) 87 | { 88 | RoseClients *rcs = rose_client_get_all(); 89 | 90 | for (int i = 0; i < rcs->n_clients; i++) { 91 | if (rcs->clients[i]->id == id) { 92 | debug("Destroying window (id: %i)", rcs->clients[i]->id); 93 | free(rcs->clients[i]); 94 | rcs->clients[i] = NULL; 95 | 96 | for (int j = i + 1; j < rcs->n_clients; j++) { 97 | rcs->clients[j - 1] = rcs->clients[j]; 98 | } 99 | 100 | rcs->n_clients--; 101 | rcs->clients = realloc(rcs->clients, rcs->n_clients * sizeof(RoseClient*)); 102 | 103 | if (rcs->n_clients == 0) 104 | exit(0); 105 | return; 106 | } 107 | } 108 | } 109 | 110 | int rose_client_lua_new(lua_State *L) 111 | { 112 | RoseClient *rc = rose_client_new(); 113 | lua_pushlightuserdata(L, rc); 114 | return 1; 115 | } 116 | 117 | void rose_client_lua_api(lua_State *L) 118 | { 119 | rose_lua_table_add_field("rose.client"); 120 | lua_pushcfunction(L, rose_client_lua_new); 121 | lua_setfield(L, -2, "new"); 122 | lua_pop(L, -2); 123 | } 124 | 125 | void rose_client_destroy_all(void) 126 | { 127 | RoseClients *rcs = rose_client_get_all(); 128 | 129 | for (int i = 0; i < rcs->n_clients; i++) { 130 | rose_window_destroy(rcs->clients[i]->window); 131 | } 132 | 133 | free(rcs->clients); 134 | } 135 | -------------------------------------------------------------------------------- /src/client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "window.h" 4 | 5 | typedef struct 6 | { 7 | RoseWindow *window; 8 | int id; 9 | } RoseClient; 10 | 11 | RoseClient *rose_client_new(void); 12 | 13 | RoseClient *rose_client_get_by_window(RoseWindow *window); 14 | RoseClient *rose_client_get_by_id(int); 15 | 16 | void rose_client_destroy_by_window(RoseWindow *window); 17 | void rose_client_destroy_by_id(int); 18 | void rose_client_destroy_all(void); 19 | void rose_client_lua_api(lua_State *L); 20 | -------------------------------------------------------------------------------- /src/debug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "debug.h" 6 | 7 | #if defined(DEBUG) 8 | void _debug(char *fmt, ...) 9 | { 10 | va_list args; 11 | va_start(args, fmt); 12 | fputs("\033[1;35mdebug\033[0m: ", stderr); 13 | vfprintf(stderr, fmt, args); 14 | fputc('\n', stderr); 15 | va_end(args); 16 | } 17 | #endif 18 | 19 | void warn(char *fmt, ...) 20 | { 21 | va_list args; 22 | va_start(args, fmt); 23 | fputs("\033[1;33mwarning\033[0m: ", stderr); 24 | vfprintf(stderr, fmt, args); 25 | fputc('\n', stderr); 26 | va_end(args); 27 | } 28 | 29 | void error(char *fmt, ...) 30 | { 31 | va_list args; 32 | va_start(args, fmt); 33 | fputs("rose: \033[1;31error\033[0m: ", stderr); 34 | vfprintf(stderr, fmt, args); 35 | fputc('\n', stderr); 36 | va_end(args); 37 | exit(1); 38 | } 39 | 40 | void info(char *fmt, ...) 41 | { 42 | va_list args; 43 | va_start(args, fmt); 44 | fputs("\033[1;34mnote\033[0m: ", stderr); 45 | vfprintf(stderr, fmt, args); 46 | fputc('\n', stderr); 47 | va_end(args); 48 | } 49 | -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | void info(char *fmt, ...); 5 | void warn(char *fmt, ...); 6 | noreturn void error(char *fmt, ...); 7 | 8 | #if defined(DEBUG) 9 | void _debug(char *fmt, ...); 10 | # define debug(...) _debug(__VA_ARGS__) 11 | #else 12 | # define debug(...) (void) __VA_ARGS__ 13 | #endif 14 | -------------------------------------------------------------------------------- /src/keymap.c: -------------------------------------------------------------------------------- 1 | #include "debug.h" 2 | #include "keymap.h" 3 | #include "lua.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static bool rose_keymap_equals(RoseKeymap *k1, RoseKeymap *k2) 10 | { 11 | return k1->keyval == k2->keyval 12 | && k1->state == k2->state; 13 | } 14 | 15 | static RoseKeymapList *rose_keymap_list_append(RoseKeymap *keymap) 16 | { 17 | RoseKeymapList *rkl = rose_keymap_list_get(); 18 | 19 | for (int i = 0; i < rkl->n_keymaps; i++) { 20 | if (rose_keymap_equals(keymap, rkl->keymaps[i])) { 21 | free(rkl->keymaps[i]); 22 | rkl->keymaps[i] = keymap; 23 | return rkl; 24 | } 25 | } 26 | 27 | rkl->keymaps = realloc(rkl->keymaps, sizeof(void *) * (rkl->n_keymaps + 1)); 28 | rkl->keymaps[rkl->n_keymaps++] = keymap; 29 | return rkl; 30 | } 31 | 32 | static void rose_keymap_list_remove(int index) 33 | { 34 | RoseKeymapList *keymap_list = rose_keymap_list_get(); 35 | 36 | if (index < 0 || index >= keymap_list->n_keymaps) { 37 | // handle index out of range error 38 | return; 39 | } 40 | 41 | free(keymap_list->keymaps[index]); 42 | 43 | memmove(keymap_list->keymaps + index, 44 | keymap_list->keymaps + index + 1, 45 | sizeof(void *) * (keymap_list->n_keymaps - index - 1)); 46 | } 47 | 48 | static int parse_single_mod_key(RoseKeymap *keymap, const char *key) 49 | { 50 | switch (*key) { 51 | case 'c': 52 | keymap->state = GDK_CONTROL_MASK; 53 | break; 54 | case 's': 55 | keymap->state = GDK_SHIFT_MASK; 56 | break; 57 | case 'a': 58 | #if GTK == 3 59 | keymap->state = GDK_MOD1_MASK; // ALT 60 | #elif GTK == 4 61 | keymap->state = GDK_ALT_MASK; 62 | #endif 63 | break; 64 | default: 65 | warn("cannot resolve modifier `%c` for `%s`", *key, key); 66 | return 1; 67 | } 68 | 69 | if (*(key + 1) != '-') { 70 | warn("expected `-` after modifier got `%s`", *(key + 1)); 71 | return 1; 72 | } 73 | 74 | keymap->keyval = gdk_keyval_from_name(key + 2); 75 | 76 | return 0; 77 | } 78 | 79 | static RoseKeymap *rose_keymap_new(const char *key) 80 | { 81 | RoseKeymap *keymap = calloc(1, sizeof(RoseKeymap)); 82 | 83 | switch (strlen(key)) { 84 | case 1: 85 | keymap->state = 0; 86 | keymap->keyval = gdk_keyval_from_name(key); 87 | break; 88 | default: 89 | if (parse_single_mod_key(keymap, key)) { 90 | free(keymap); 91 | return NULL; 92 | } 93 | break; 94 | } 95 | 96 | return keymap; 97 | } 98 | 99 | void rose_keymap_set(lua_State *L) 100 | { 101 | // Retrieve the first argument from Lua as a string 102 | const char *key = luaL_checkstring(L, 1); 103 | 104 | // Create a new RoseKeymap using the key 105 | RoseKeymap *keymap = rose_keymap_new(key); 106 | 107 | // Check if the second argument is a Lua function 108 | if (!lua_isfunction(L, 2)) 109 | error("expected lua function in rose.keymap.set"); 110 | 111 | // Retrieve the Lua function from the stack and assign it to the keymap 112 | keymap->func = lua_tocfunction(L, 2); 113 | 114 | // Remove the Lua function from the stack 115 | lua_pop(L, 1); 116 | 117 | // Remove the Lua function from the stack 118 | rose_keymap_list_append(keymap); 119 | } 120 | 121 | RoseKeymapList *rose_keymap_list_get(void) 122 | { 123 | static RoseKeymapList *rkl = NULL; 124 | 125 | if (rkl == NULL) 126 | rkl = calloc(1, sizeof(RoseKeymapList)); 127 | 128 | return rkl; 129 | } 130 | 131 | void rose_keymap_del(lua_State *L) 132 | { 133 | const char *key = luaL_checkstring(L, 1); 134 | RoseKeymapList *keymap_list = rose_keymap_list_get(); 135 | RoseKeymap *keymap = rose_keymap_new(key); 136 | 137 | for (int i = 0; i < keymap_list->n_keymaps; i++) { 138 | if (rose_keymap_equals(keymap_list->keymaps[i], keymap)) { 139 | rose_keymap_list_remove(i); 140 | return; 141 | } 142 | } 143 | } 144 | 145 | void rose_keymap_lua_api(lua_State *L) 146 | { 147 | rose_lua_table_add_field("rose.keymap"); 148 | lua_pushcfunction(L, (lua_CFunction) rose_keymap_set); 149 | lua_setfield(L, -2, "set"); 150 | 151 | rose_lua_value("rose.keymap"); 152 | lua_pushcfunction(L, (lua_CFunction) rose_keymap_del); 153 | lua_setfield(L, -2, "del"); 154 | } 155 | 156 | void rose_keymap_list_destroy(RoseKeymapList *rkl) 157 | { 158 | for (int i = 0; i < rkl->n_keymaps; i++) 159 | free(rkl->keymaps[i]); 160 | 161 | free(rkl); 162 | } 163 | -------------------------------------------------------------------------------- /src/keymap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct 6 | { 7 | int state; 8 | int keyval; 9 | lua_CFunction func; 10 | } RoseKeymap; 11 | 12 | typedef struct 13 | { 14 | RoseKeymap **keymaps; 15 | int n_keymaps; 16 | } RoseKeymapList; 17 | 18 | void rose_keymap_set(struct lua_State *L); 19 | RoseKeymapList *rose_keymap_list_get(void); 20 | void rose_keymap_lua_api(lua_State *L); 21 | -------------------------------------------------------------------------------- /src/lua.c: -------------------------------------------------------------------------------- 1 | #include "lua.h" 2 | #include "debug.h" 3 | #include "keymap.h" 4 | #include "path.h" 5 | #include "webview.h" 6 | #include "keymap.h" 7 | #include "window.h" 8 | #include "client.h" 9 | #include "tab.h" 10 | #include "search.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | void rose_lua_value(char *fieldpath) 17 | { 18 | lua_State *L = rose_lua_state_get(); 19 | 20 | char *path = strdup(fieldpath); 21 | char *path_chunk = strtok(path, "."); 22 | 23 | lua_getglobal(L, path_chunk); 24 | 25 | if (lua_isnil(L, -1)) { 26 | info("cannot access `%s`, the `%s` does not exist", fieldpath, path_chunk); 27 | free(path); 28 | return; 29 | } 30 | 31 | while (path_chunk != NULL) { 32 | path_chunk = strtok(NULL, "."); 33 | 34 | if (path_chunk == NULL) 35 | break; 36 | 37 | lua_getfield(L, -1, path_chunk); 38 | 39 | if (lua_isnil(L, -1)) { 40 | info("cannot access `%s`, the field `%s` does not exist", fieldpath, path_chunk); 41 | free(path); 42 | return; 43 | } 44 | } 45 | 46 | free(path); 47 | } 48 | 49 | static void rose_lua_setup(void) 50 | { 51 | lua_State *L = rose_lua_state_get(); 52 | rose_lua_add_table("rose"); 53 | rose_lua_table_add_field("rose.webkit.settings"); 54 | rose_window_lua_api(L); 55 | rose_webview_lua_api(L); 56 | rose_keymap_lua_api(L); 57 | rose_client_lua_api(L); 58 | rose_tab_lua_api(L); 59 | rose_search_lua_api(L); 60 | } 61 | 62 | void rose_lua_add_table(char *name) 63 | { 64 | lua_State *L = rose_lua_state_get(); 65 | 66 | lua_getglobal(L, name); 67 | 68 | if (!lua_isnil(L, -1)) { 69 | warn("overrides `%s` table", name); 70 | } 71 | 72 | lua_pop(L, -1); 73 | lua_newtable(L); 74 | lua_setglobal(L, name); 75 | } 76 | 77 | bool rose_lua_value_boolean(char *fieldpath) 78 | { 79 | lua_State *L = rose_lua_state_get(); 80 | rose_lua_value(fieldpath); 81 | 82 | if (lua_isnil(L, -1)) 83 | return false; 84 | 85 | bool value = lua_toboolean(L, -1); 86 | lua_pop(L, -1); 87 | return value; 88 | } 89 | 90 | char *rose_lua_value_string(char *fieldpath) 91 | { 92 | lua_State *L = rose_lua_state_get(); 93 | 94 | rose_lua_value(fieldpath); 95 | 96 | if (lua_isnil(L, -1)) 97 | return NULL; 98 | 99 | char *value = strdup(lua_tostring(L, -1)); 100 | lua_pop(L, -1); 101 | return value; 102 | } 103 | 104 | void *rose_lua_value_ptr(char *fieldpath) 105 | { 106 | lua_State *L = rose_lua_state_get(); 107 | 108 | rose_lua_value(fieldpath); 109 | 110 | if (lua_isnil(L, -1)) 111 | return NULL; 112 | 113 | void *ptr = *((void **)lua_touserdata(L, -1)); 114 | lua_pop(L, -1); 115 | return ptr; 116 | } 117 | 118 | int rose_lua_value_number(char *fieldpath) 119 | { 120 | lua_State *L = rose_lua_state_get(); 121 | 122 | rose_lua_value(fieldpath); 123 | 124 | if (lua_isnil(L, -1)) 125 | return -1; 126 | 127 | int value = lua_tonumber(L, -1); 128 | lua_pop(L, -1); 129 | return value; 130 | } 131 | 132 | void rose_lua_table_add_field(const char *fieldpath) 133 | { 134 | lua_State *L = rose_lua_state_get(); 135 | 136 | // Split the fieldpath string into parts 137 | char* path_copy = strdup(fieldpath); 138 | char* path_part = strtok(path_copy, "."); 139 | 140 | lua_getglobal(L, path_part); 141 | 142 | path_part = strtok(NULL, "."); 143 | 144 | // Traverse the fieldpath and create nested tables as needed 145 | while (path_part != NULL) { 146 | // Push the next table onto the stack or create it if it doesn't exist 147 | lua_pushstring(L, path_part); 148 | lua_gettable(L, -2); 149 | if (lua_isnil(L, -1)) { 150 | lua_pop(L, 1); 151 | lua_newtable(L); 152 | lua_pushstring(L, path_part); 153 | lua_pushvalue(L, -2); 154 | lua_settable(L, -4); 155 | } 156 | // Move to the next part of the fieldpath 157 | path_part = strtok(NULL, "."); 158 | } 159 | 160 | // Clean up 161 | free(path_copy); 162 | } 163 | 164 | lua_State *rose_lua_state_get() 165 | { 166 | static lua_State *L = NULL; 167 | 168 | // Create state if not already exist 169 | if (L == NULL) { 170 | L = luaL_newstate(); 171 | luaL_openlibs(L); 172 | rose_lua_setup(); 173 | char *config_path = buildpath(getenv("HOME"), ".config/rose/init.lua", NULL); 174 | if (luaL_dofile(L, config_path) != 0) { 175 | warn("%s", lua_tostring(L, -1)); 176 | } 177 | free(config_path); 178 | } 179 | 180 | return L; 181 | } 182 | -------------------------------------------------------------------------------- /src/lua.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | lua_State *rose_lua_state_get(); 9 | void rose_lua_add_table(char *name); 10 | void rose_lua_table_add_field(const char* fieldpath); 11 | bool rose_lua_value_boolean(char *fieldpath); 12 | char *rose_lua_value_string(char *fieldpath); 13 | void rose_lua_value(char *fieldpath); 14 | int rose_lua_value_number(char *fieldpath); 15 | void *rose_lua_value_ptr(char *fieldpath); 16 | -------------------------------------------------------------------------------- /src/path.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | char *buildpath(char *dir, ...) 6 | { 7 | char path[PATH_MAX]; 8 | char *path_part; 9 | 10 | va_list args; 11 | va_start(args, dir); 12 | 13 | *path = 0; 14 | strcat(path, dir); 15 | 16 | path_part = va_arg(args, char *); 17 | while (path_part != NULL) { 18 | strcat(path, "/"); 19 | strcat(path, path_part); 20 | path_part = va_arg(args, char *); 21 | } 22 | 23 | va_end(args); 24 | return strdup(path); 25 | } 26 | -------------------------------------------------------------------------------- /src/path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* Returns path string */ 4 | char *buildpath(char *dir, ...); 5 | -------------------------------------------------------------------------------- /src/rose.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | #include "lua.h" 3 | #include 4 | #include 5 | 6 | void at_exit() 7 | { 8 | rose_client_destroy_all(); 9 | } 10 | 11 | void rose_setup_gtk() 12 | { 13 | g_object_set( 14 | gtk_settings_get_default(), 15 | "gtk-enable-animations", 16 | rose_lua_value_boolean("rose.settings.animations"), 17 | "gtk-application-prefer-dark-theme", 18 | rose_lua_value_boolean("rose.settings.darkmode"), 19 | NULL); 20 | } 21 | 22 | int main() 23 | { 24 | /* Setup */ 25 | #if GTK == 3 26 | gtk_init(NULL, NULL); 27 | #elif GTK == 4 28 | gtk_init(); 29 | #endif 30 | srand(time(NULL)); 31 | atexit(at_exit); 32 | 33 | signal(SIGINT, (void *)rose_client_destroy_all); 34 | signal(SIGTERM, (void *)rose_client_destroy_all); 35 | signal(SIGKILL, (void *)rose_client_destroy_all); 36 | 37 | rose_client_new(); 38 | rose_setup_gtk(); 39 | 40 | while (1) 41 | g_main_context_iteration(NULL, TRUE); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /src/search.c: -------------------------------------------------------------------------------- 1 | #include "search.h" 2 | #include "lua.h" 3 | #include "window.h" 4 | #include "webview.h" 5 | 6 | #include 7 | #include 8 | 9 | static GtkWidget *last_focused = NULL; 10 | 11 | #if GTK == 4 12 | static GtkEntryBuffer *entry_buffer = NULL; 13 | #endif 14 | 15 | void rose_search_cb(GtkWidget *entry, gpointer data) 16 | { 17 | RoseWindow *rw = rose_window_get(); 18 | 19 | #if GTK == 3 20 | GtkWidget *dialog = GTK_WIDGET(data); 21 | const char *uri = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); 22 | gtk_widget_destroy(dialog); 23 | #elif GTK == 4 24 | (void) data, (void) entry; 25 | const char *uri = strdup(gtk_entry_buffer_get_text(entry_buffer)); 26 | #endif 27 | 28 | gtk_window_set_focus(rw->window, last_focused); 29 | rose_webview_load_uri(uri); 30 | free((char *)uri); 31 | } 32 | 33 | int rose_search_open(lua_State *L) 34 | { 35 | (void) L; 36 | 37 | RoseWindow *rw = rose_window_get(); 38 | last_focused = gtk_window_get_focus(rw->window); 39 | 40 | 41 | #if GTK == 3 42 | GtkWidget *dialog = gtk_dialog_new(); 43 | 44 | gtk_window_set_transient_for(GTK_WINDOW(dialog), rw->window); 45 | gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); 46 | 47 | GtkWidget *entry = gtk_entry_new(); 48 | gtk_container_add( 49 | GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), 50 | entry 51 | ); 52 | 53 | g_signal_connect(entry, "activate", G_CALLBACK(rose_search_cb), dialog); 54 | 55 | gtk_widget_show_all(dialog); 56 | #elif GTK == 4 57 | entry_buffer = gtk_entry_buffer_new("", 0); 58 | GtkWidget *entry = gtk_entry_new_with_buffer(entry_buffer); 59 | 60 | GtkWindow *popup = GTK_WINDOW(gtk_window_new()); 61 | gtk_window_set_child(popup, entry); 62 | 63 | g_signal_connect(entry, "activate", G_CALLBACK(rose_search_cb), NULL); 64 | gtk_window_present(popup); 65 | #endif 66 | 67 | return 0; 68 | } 69 | 70 | void rose_search_lua_api(lua_State *L) 71 | { 72 | rose_lua_value("rose.webview"); 73 | lua_pushcfunction(L, (lua_CFunction) rose_search_open); 74 | lua_setfield(L, -2, "search"); 75 | } 76 | -------------------------------------------------------------------------------- /src/search.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void rose_search_lua_api(lua_State *L); 6 | -------------------------------------------------------------------------------- /src/split.c: -------------------------------------------------------------------------------- 1 | #include "window.h" 2 | #include "debug.h" 3 | #include "webview.h" 4 | #include "lua.h" 5 | #include 6 | 7 | static void rose_split(GtkOrientation orientation) 8 | { 9 | RoseWindow *rw = rose_window_get(); 10 | GtkWidget *focus = gtk_window_get_focus(rw->window); 11 | 12 | if (focus == NULL || GTK_IS_WINDOW(focus)) 13 | return; 14 | 15 | GtkBox *outside = GTK_BOX(gtk_widget_get_parent(focus)); 16 | 17 | if (outside == NULL) 18 | return; 19 | 20 | gtk_orientable_set_orientation(GTK_ORIENTABLE(outside), orientation); 21 | 22 | GtkBox *inside = GTK_BOX(gtk_box_new(orientation, 0)); 23 | #if GTK == 3 24 | gtk_box_pack_start(inside, GTK_WIDGET(rose_webview_new()), true, true, 0); 25 | gtk_box_pack_start(outside, GTK_WIDGET(inside), true, true, 0); 26 | #elif GTK == 4 27 | gtk_box_append(inside, GTK_WIDGET(rose_webview_new())); 28 | gtk_box_append(outside, GTK_WIDGET(inside)); 29 | #endif 30 | 31 | #if GTK == 3 32 | gtk_widget_show_all(GTK_WIDGET(rw->window)); 33 | #endif 34 | } 35 | 36 | void rose_window_vsplit(lua_State *L) 37 | { 38 | (void) L; 39 | rose_split(GTK_ORIENTATION_HORIZONTAL); 40 | } 41 | 42 | void rose_window_hsplit(lua_State *L) 43 | { 44 | (void) L; 45 | rose_split(GTK_ORIENTATION_VERTICAL); 46 | } 47 | 48 | void rose_window_split_close(lua_State *L) 49 | { 50 | (void) L; 51 | 52 | RoseWindow *rw = rose_window_get(); 53 | GtkWidget *focused = gtk_window_get_focus(rw->window); 54 | 55 | GtkWidget *layout_manager = gtk_widget_get_parent(focused); 56 | 57 | while (layout_manager && !GTK_IS_BOX(layout_manager)) 58 | layout_manager = gtk_widget_get_parent(layout_manager); 59 | 60 | GtkWidget *main_box = gtk_widget_get_parent(focused); 61 | GtkWidget *buf = main_box; 62 | while (1) { 63 | if (buf == NULL) 64 | break; 65 | 66 | if (GTK_IS_BOX(buf)) 67 | main_box = buf; 68 | 69 | buf = gtk_widget_get_parent(buf); 70 | } 71 | 72 | #if GTK == 3 73 | GList *main_box_children = gtk_container_get_children(GTK_CONTAINER(main_box)); 74 | int main_box_n_child = g_list_length(main_box_children); 75 | 76 | if (layout_manager != NULL && main_box_n_child > 1) 77 | gtk_container_remove(GTK_CONTAINER(layout_manager), focused); 78 | 79 | GList *layout_children = gtk_container_get_children(GTK_CONTAINER(layout_manager)); 80 | int layout_n_child = g_list_length(layout_children); 81 | 82 | if (layout_n_child == 0) { 83 | gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(layout_manager)), layout_manager); 84 | } 85 | 86 | g_list_free(main_box_children); 87 | #elif GTK == 4 88 | error("not implemented yet."); 89 | #endif 90 | } 91 | -------------------------------------------------------------------------------- /src/split.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void rose_window_hsplit(lua_State *L); 6 | void rose_window_vsplit(lua_State *L); 7 | void rose_window_split_close(lua_State *L); 8 | -------------------------------------------------------------------------------- /src/tab.c: -------------------------------------------------------------------------------- 1 | #include "tab.h" 2 | 3 | #include "debug.h" 4 | #include "lua.h" 5 | #include "window.h" 6 | #include "webview.h" 7 | #include 8 | #include 9 | 10 | int rose_tab_new(lua_State *L) 11 | { 12 | (void) L; 13 | 14 | RoseWindow *rw = rose_window_get(); 15 | GtkStack *stack = rw->stack; 16 | 17 | GtkBox *box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); 18 | #if GTK == 3 19 | gtk_box_pack_start(box, GTK_WIDGET(rose_webview_new()), true, true, 0); 20 | #elif GTK == 4 21 | gtk_box_append(box, GTK_WIDGET(rose_webview_new())); 22 | #endif 23 | gtk_stack_add_named(stack, GTK_WIDGET(box), (char[]) { rand(), 0 }); 24 | 25 | #if GTK == 3 26 | gtk_widget_show_all(GTK_WIDGET(rw->window)); 27 | #endif 28 | gtk_stack_set_visible_child(stack, GTK_WIDGET(box)); 29 | 30 | return 0; 31 | } 32 | 33 | #if GTK == 4 34 | gint get_index_from_list_model(GListModel *list_model, gpointer item) 35 | { 36 | gint index = -1; 37 | guint n_items = g_list_model_get_n_items(list_model); 38 | 39 | for (guint i = 0; i < n_items; i++) { 40 | gpointer current_item = g_list_model_get_item(list_model, i); 41 | if (current_item == item) { 42 | index = i; 43 | break; 44 | } 45 | } 46 | 47 | return index; 48 | } 49 | #endif 50 | 51 | int rose_tab_next(lua_State *L) 52 | { 53 | (void) L; 54 | 55 | RoseWindow *rw = rose_window_get(); 56 | GtkStack *stack = rw->stack; 57 | 58 | GtkWidget *visible = gtk_stack_get_visible_child(stack); 59 | 60 | if (visible != NULL) { 61 | #if GTK == 3 62 | GList *pages = gtk_container_get_children(GTK_CONTAINER(stack)); 63 | int index = (g_list_index(pages, visible) + 1) % g_list_length(pages); 64 | gtk_stack_set_visible_child(stack, g_list_nth_data(pages, index)); 65 | g_list_free(pages); 66 | #elif GTK == 4 67 | GListModel *pages = G_LIST_MODEL(gtk_stack_get_pages(stack)); 68 | int index = (get_index_from_list_model(pages, visible) + 1) % g_list_model_get_n_items(pages); 69 | gtk_stack_set_visible_child(stack, g_list_model_get_item(pages, index)); 70 | #endif 71 | } 72 | 73 | return 0; 74 | } 75 | 76 | int rose_tab_prev(lua_State *L) 77 | { 78 | (void) L; 79 | 80 | RoseWindow *rw = rose_window_get(); 81 | GtkStack *stack = rw->stack; 82 | 83 | GtkWidget *visible = gtk_stack_get_visible_child(stack); 84 | 85 | if (visible != NULL) { 86 | #if GTK == 3 87 | GList *pages = gtk_container_get_children(GTK_CONTAINER(stack)); 88 | gint index = g_list_index(pages, visible); 89 | #elif GTK == 4 90 | GListModel *pages = G_LIST_MODEL(gtk_stack_get_pages(stack)); 91 | int index = (get_index_from_list_model(pages, visible) + 1) % g_list_model_get_n_items(pages); 92 | #endif 93 | 94 | if (index == 0) { 95 | #if GTK == 3 96 | index = g_list_length(pages) - 1; 97 | #elif GTK == 4 98 | index = g_list_model_get_n_items(pages) - 1; 99 | #endif 100 | } else { 101 | index--; 102 | } 103 | 104 | #if GTK == 3 105 | gtk_stack_set_visible_child(stack, g_list_nth_data(pages, index)); 106 | g_list_free(pages); 107 | #elif GTK == 4 108 | gtk_stack_set_visible_child(stack, g_list_model_get_item(pages, index)); 109 | #endif 110 | } 111 | 112 | return 0; 113 | } 114 | 115 | int rose_tab_close(lua_State *L) 116 | { 117 | (void) L; 118 | RoseWindow *rw = rose_window_get(); 119 | GtkStack *stack = rw->stack; 120 | GtkWidget *page = gtk_stack_get_visible_child(stack); 121 | #if GTK == 3 122 | gtk_container_remove(GTK_CONTAINER(stack), page); 123 | #elif GTK == 4 124 | gtk_stack_remove(stack, page); 125 | #endif 126 | return 0; 127 | } 128 | 129 | void rose_tab_lua_api(lua_State *L) 130 | { 131 | rose_lua_table_add_field("rose.tab"); 132 | lua_pushcfunction(L, rose_tab_new); 133 | lua_setfield(L, -2, "new"); 134 | 135 | rose_lua_value("rose.tab"); 136 | lua_pushcfunction(L, rose_tab_close); 137 | lua_setfield(L, -2, "close"); 138 | 139 | rose_lua_value("rose.tab"); 140 | lua_pushcfunction(L, rose_tab_next); 141 | lua_setfield(L, -2, "next"); 142 | 143 | rose_lua_value("rose.tab"); 144 | lua_pushcfunction(L, rose_tab_prev); 145 | lua_setfield(L, -2, "prev"); 146 | } 147 | -------------------------------------------------------------------------------- /src/tab.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void rose_tab_lua_api(lua_State *L); 6 | -------------------------------------------------------------------------------- /src/webview.c: -------------------------------------------------------------------------------- 1 | #include "webview.h" 2 | #include "lua.h" 3 | #include "window.h" 4 | #include "debug.h" 5 | 6 | void rose_webview_run_javascript(gchar *script); 7 | 8 | static WebKitSettings *rose_webview_get_settings(void) 9 | { 10 | static WebKitSettings *settings = NULL; 11 | 12 | if (settings == NULL) { 13 | settings = webkit_settings_new_with_settings( 14 | "enable-back-forward-navigation-gestures", 15 | rose_lua_value_boolean("rose.webkit.settings.gestures"), 16 | "allow-file-access-from-file-urls", 17 | rose_lua_value_boolean("rose.webkit.settings.allow_file_access_from_urls"), 18 | "enable-developer-extras", 19 | rose_lua_value_boolean("rose.webkit.settings.developer_extras"), 20 | "enable-webgl", 21 | rose_lua_value_boolean("rose.webkit.settings.webgl"), 22 | "enable-smooth-scrolling", 23 | rose_lua_value_boolean("rose.webkit.settings.smooth_scrolling"), 24 | NULL 25 | ); 26 | } 27 | 28 | return settings; 29 | } 30 | 31 | WebKitWebView *rose_webview_new(void) 32 | { 33 | WebKitWebView *view = WEBKIT_WEB_VIEW(webkit_web_view_new()); 34 | char *uri = rose_lua_value_string("rose.startpage"); 35 | 36 | if (uri == NULL) { 37 | webkit_web_view_load_uri(view, "https://duckduckgo.com"); 38 | } else { 39 | webkit_web_view_load_uri(view, uri); 40 | free(uri); 41 | } 42 | 43 | webkit_web_view_set_settings(view, rose_webview_get_settings()); 44 | 45 | return view; 46 | } 47 | 48 | static WebKitWebView *rose_webview_get(void) 49 | { 50 | RoseWindow *rw = rose_window_get(); 51 | GtkWidget *focus = gtk_window_get_focus(rw->window); 52 | 53 | return WEBKIT_WEB_VIEW(focus); 54 | } 55 | 56 | void rose_webview_run_javascript(gchar *script) 57 | { 58 | webkit_web_view_evaluate_javascript( 59 | rose_webview_get(), 60 | script, -1, NULL, NULL, NULL, 61 | NULL, NULL 62 | ); 63 | } 64 | 65 | void rose_webview_scroll_to_bottom() 66 | { 67 | rose_webview_run_javascript("window.scrollTo(0, document.body.scrollHeight);"); 68 | } 69 | 70 | void rose_webview_scroll_to_top() 71 | { 72 | rose_webview_run_javascript("window.scrollTo(0, 0);"); 73 | } 74 | 75 | void rose_webview_scroll_down() 76 | { 77 | rose_webview_run_javascript("window.scrollBy(0, 100);"); 78 | } 79 | 80 | void rose_webview_scroll_up() 81 | { 82 | rose_webview_run_javascript("window.scrollBy(0, -100);"); 83 | } 84 | 85 | void rose_webview_scroll_left() 86 | 87 | { 88 | rose_webview_run_javascript("window.scrollBy(-100, 0);"); 89 | } 90 | 91 | void rose_webview_scroll_right() 92 | { 93 | rose_webview_run_javascript("window.scrollBy(100, 0);"); 94 | } 95 | 96 | void rose_webview_scroll_page_down() 97 | { 98 | rose_webview_run_javascript("window.scrollBy(0, window.innerHeight);"); 99 | } 100 | 101 | void rose_webview_scroll_page_up() 102 | { 103 | rose_webview_run_javascript("window.scrollBy(0, -window.innerHeight);"); 104 | } 105 | 106 | void rose_webview_scroll_page_left() 107 | { 108 | rose_webview_run_javascript("window.scrollBy(-window.innerWidth, 0);"); 109 | } 110 | 111 | void rose_webview_scroll_page_right() 112 | { 113 | rose_webview_run_javascript("window.scrollBy(window.innerWidth, 0);"); 114 | } 115 | 116 | void rose_webview_evaluate_javascript(lua_State *L) 117 | { 118 | char *script = (char *) luaL_checkstring(L, 1); 119 | 120 | if (script == NULL) { 121 | return; 122 | } 123 | 124 | rose_webview_run_javascript(script); 125 | } 126 | 127 | void rose_webview_load_uri(const char *uri) 128 | { 129 | if (g_str_has_prefix(uri, "http://") || g_str_has_prefix(uri, "https://") || 130 | g_str_has_prefix(uri, "file://") || g_str_has_prefix(uri, "about:")) { 131 | webkit_web_view_load_uri(rose_webview_get(), uri); 132 | } else { 133 | char *search_uri = rose_lua_value_string("rose.search"); 134 | 135 | if (search_uri == NULL) { 136 | search_uri = strdup("https://duckduckgo.com/?q=%s"); 137 | } 138 | 139 | int lenght = strlen(uri) + strlen(search_uri) + 1; 140 | char *tmp = calloc(1, lenght); 141 | snprintf(tmp, lenght - 1, search_uri, uri); 142 | webkit_web_view_load_uri(rose_webview_get(), tmp); 143 | free(tmp); 144 | free(search_uri); 145 | } 146 | } 147 | 148 | void rose_webview_reload(void) 149 | { 150 | webkit_web_view_reload(rose_webview_get()); 151 | } 152 | 153 | void rose_webview_goback(void) 154 | { 155 | webkit_web_view_go_back(rose_webview_get()); 156 | } 157 | 158 | void rose_webview_goforward(void) 159 | { 160 | webkit_web_view_go_forward(rose_webview_get()); 161 | } 162 | 163 | void rose_webview_open(lua_State *L) 164 | { 165 | const char *uri = luaL_checkstring(L, 1); 166 | rose_webview_load_uri(uri); 167 | } 168 | 169 | void rose_webview_zoomin(void) 170 | { 171 | double zoom = webkit_web_view_get_zoom_level(rose_webview_get()); 172 | webkit_web_view_set_zoom_level(rose_webview_get(), zoom += 0.1); 173 | } 174 | 175 | void rose_webview_zoomout(void) 176 | { 177 | double zoom = webkit_web_view_get_zoom_level(rose_webview_get()); 178 | webkit_web_view_set_zoom_level(rose_webview_get(), zoom -= 0.1); 179 | } 180 | 181 | void rose_webview_zoom_reset(void) 182 | { 183 | webkit_web_view_set_zoom_level(rose_webview_get(), 1); 184 | } 185 | 186 | void rose_webview_force_reload(void) 187 | { 188 | webkit_web_view_reload_bypass_cache(rose_webview_get()); 189 | } 190 | 191 | void rose_webview_lua_api(lua_State *L) 192 | { 193 | struct { 194 | void *func; 195 | char *name; 196 | } api[] = { 197 | { rose_webview_open, "open" }, 198 | { rose_webview_reload, "reload" }, 199 | { rose_webview_force_reload, "force_reload" }, 200 | { rose_webview_goback, "goback" }, 201 | { rose_webview_goforward, "goforward" }, 202 | { rose_webview_zoomout, "zoomout" }, 203 | { rose_webview_zoomin, "zoomin" }, 204 | { rose_webview_load_uri, "load_uri" }, 205 | { rose_webview_zoom_reset, "zoom_reset" }, 206 | { rose_webview_scroll_to_bottom, "scroll_to_bottom" }, 207 | { rose_webview_scroll_to_top, "scroll_to_top" }, 208 | { rose_webview_scroll_down, "scroll_down" }, 209 | { rose_webview_scroll_up, "scroll_up" }, 210 | { rose_webview_scroll_left, "scroll_left" }, 211 | { rose_webview_scroll_right, "scroll_right" }, 212 | { rose_webview_scroll_page_down, "scroll_page_down" }, 213 | { rose_webview_scroll_page_up, "scroll_page_up" }, 214 | { rose_webview_scroll_page_left, "scroll_page_left" }, 215 | { rose_webview_scroll_page_right, "scroll_page_right" }, 216 | { rose_webview_evaluate_javascript, "evaluate_javascript" } 217 | }; 218 | 219 | for (ulong i = 0; i < sizeof(api) / sizeof(api[0]); i++) { 220 | rose_lua_table_add_field("rose.webview"); 221 | lua_pushcfunction(L, (lua_CFunction) api[i].func); 222 | lua_setfield(L, -2, api[i].name); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/webview.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if GTK == 4 6 | #include 7 | #elif GTK == 3 8 | #include 9 | #else 10 | # error "Unknown gtk version" 11 | #endif 12 | 13 | WebKitWebView *rose_webview_new(void); 14 | void rose_webview_load_uri(const char *); 15 | void rose_webview_lua_api(lua_State *); 16 | -------------------------------------------------------------------------------- /src/window.c: -------------------------------------------------------------------------------- 1 | #include "window.h" 2 | #include "client.h" 3 | #include "lua.h" 4 | #include "webview.h" 5 | #include "debug.h" 6 | #include "keymap.h" 7 | #include "split.h" 8 | 9 | #if GTK == 3 10 | static void rose_window_state_event(void *win, GdkEventWindowState *event); 11 | #endif 12 | 13 | static void rose_window_destroy_cb(GtkWindow *window, RoseWindow *rw) 14 | { 15 | (void) window; 16 | rose_client_destroy_by_window(rw); 17 | } 18 | 19 | static int rose_handle_key(RoseKeymap *rk) 20 | { 21 | lua_State *L = rose_lua_state_get(); 22 | rk->func(L); 23 | return 1; 24 | } 25 | 26 | #if GTK == 3 27 | static int rose_keypress_event(void *self, GdkEvent *e) 28 | { 29 | (void) self; 30 | RoseKeymapList *rkl = rose_keymap_list_get(); 31 | GdkEventKey key = e->key; 32 | 33 | for (int i = 0; i < rkl->n_keymaps; i++) { 34 | if ((int) key.state == rkl->keymaps[i]->state 35 | && (int) key.keyval == rkl->keymaps[i]->keyval) 36 | return rose_handle_key(rkl->keymaps[i]); 37 | } 38 | 39 | return 0; 40 | } 41 | #elif GTK == 4 42 | static int rose_keypress_event(void *self, int keyval, int keycode, 43 | GdkModifierType state, void *controller) 44 | { 45 | (void) self, (void) keycode, (void) controller; 46 | 47 | RoseKeymapList *rkl = rose_keymap_list_get(); 48 | 49 | for (int i = 0; i < rkl->n_keymaps; i++) { 50 | if ((int) state == rkl->keymaps[i]->state 51 | && (int) keyval == rkl->keymaps[i]->keyval) 52 | return rose_handle_key(rkl->keymaps[i]); 53 | } 54 | 55 | return 0; 56 | } 57 | #endif 58 | 59 | RoseWindow *rose_window_get(void) 60 | { 61 | static RoseWindow *rw = NULL; 62 | 63 | if (rw == NULL) 64 | rw = rose_window_new(); 65 | 66 | return rw; 67 | } 68 | 69 | RoseWindow *rose_window_new(void) 70 | { 71 | RoseWindow *rw = calloc(1, sizeof(RoseWindow)); 72 | GtkBox *box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); 73 | 74 | #if GTK == 3 75 | rw->window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); 76 | #elif GTK == 4 77 | rw->window = GTK_WINDOW(gtk_window_new()); 78 | #endif 79 | 80 | rw->stack = GTK_STACK(gtk_stack_new()); 81 | 82 | // Set window size 83 | int width = rose_lua_value_number("rose.settings.width"); 84 | int height = rose_lua_value_number("rose.settings.height"); 85 | 86 | width = (width == -1) ? 800 : width; 87 | height = (height == -1) ? 600 : height; 88 | 89 | gtk_window_set_default_size(rw->window, width, height); 90 | 91 | // Connect signals 92 | #if GTK == 3 93 | g_signal_connect(rw->window, "key-press-event", 94 | G_CALLBACK(rose_keypress_event), NULL); 95 | #elif GTK == 4 96 | GtkEventController *event_controller = gtk_event_controller_key_new(); 97 | g_signal_connect(event_controller, "key-pressed", G_CALLBACK(rose_keypress_event), NULL); 98 | gtk_widget_add_controller(GTK_WIDGET(rw->window), event_controller); 99 | #endif 100 | 101 | #if GTK == 3 102 | gtk_container_add(GTK_CONTAINER(rw->window), GTK_WIDGET(rw->stack)); 103 | gtk_box_pack_start(box, GTK_WIDGET(rose_webview_new()), true, true, 0); 104 | gtk_stack_add_named(rw->stack, GTK_WIDGET(box), "0"); 105 | gtk_widget_show_all(GTK_WIDGET(rw->window)); 106 | g_signal_connect(rw->window, "window-state-event", G_CALLBACK(rose_window_state_event), NULL); 107 | #elif GTK == 4 108 | gtk_window_set_child(rw->window, GTK_WIDGET(rw->stack)); 109 | gtk_box_prepend(box, GTK_WIDGET(rose_webview_new())); 110 | gtk_box_set_homogeneous(box, true); 111 | gtk_stack_add_child(rw->stack, GTK_WIDGET(box)); 112 | gtk_window_present(rw->window); 113 | #endif 114 | 115 | g_signal_connect(rw->window, "destroy", 116 | G_CALLBACK(rose_window_destroy_cb), rw); 117 | 118 | return rw; 119 | } 120 | 121 | #if GTK == 3 122 | 123 | static bool rose_window_is_fullscreen(int v) 124 | { 125 | static bool is_fullscreen = false; 126 | 127 | if (v == 1 || v == 0) 128 | is_fullscreen = v; 129 | 130 | return is_fullscreen; 131 | } 132 | 133 | static void rose_window_state_event(void *win, GdkEventWindowState *event) 134 | { 135 | (void) win; 136 | 137 | if (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) 138 | rose_window_is_fullscreen(1); 139 | else 140 | rose_window_is_fullscreen(0); 141 | } 142 | 143 | #endif 144 | 145 | void rose_window_fullscreen() 146 | { 147 | RoseWindow *rw = rose_window_get(); 148 | #if GTK == 4 149 | if (gtk_window_is_fullscreen(rw->window)) { 150 | #elif GTK == 3 151 | if (rose_window_is_fullscreen(-1)) { 152 | #endif 153 | gtk_window_unfullscreen(rw->window); 154 | } else { 155 | gtk_window_fullscreen(rw->window); 156 | } 157 | } 158 | 159 | #if GTK == 4 160 | void rose_window_minimize() 161 | { 162 | RoseWindow *rw = rose_window_get(); 163 | static bool minimalized = false; 164 | 165 | if (minimalized) { 166 | minimalized = true; 167 | gtk_window_minimize(rw->window); 168 | } else { 169 | gtk_window_unminimize(rw->window); 170 | } 171 | } 172 | #endif 173 | 174 | void rose_window_maximize() 175 | { 176 | RoseWindow *rw = rose_window_get(); 177 | 178 | if (gtk_window_is_maximized(rw->window)) { 179 | gtk_window_unmaximize(rw->window); 180 | } else { 181 | gtk_window_maximize(rw->window); 182 | } 183 | } 184 | 185 | void rose_window_lua_api(lua_State *L) 186 | { 187 | rose_lua_table_add_field("rose.window.toggle"); 188 | lua_pushcfunction(L, (lua_CFunction) rose_window_fullscreen); 189 | lua_setfield(L, -2, "fullscreen"); 190 | 191 | #if GTK == 4 192 | rose_lua_table_add_field("rose.window.toggle"); 193 | lua_pushcfunction(L, (lua_CFunction) rose_window_minimize); 194 | lua_setfield(L, -2, "minimize"); 195 | #endif 196 | 197 | rose_lua_table_add_field("rose.window.toggle"); 198 | lua_pushcfunction(L, (lua_CFunction) rose_window_maximize); 199 | lua_setfield(L, -2, "maximize"); 200 | 201 | /** 202 | * TODO: create one function for splits 203 | * rose.window.split({ orientation = "horizontal|vertical" }) 204 | */ 205 | rose_lua_table_add_field("rose.window"); 206 | lua_pushcfunction(L, (lua_CFunction) rose_window_vsplit); 207 | lua_setfield(L, -2, "vsplit"); 208 | 209 | rose_lua_table_add_field("rose.window"); 210 | lua_pushcfunction(L, (lua_CFunction) rose_window_hsplit); 211 | lua_setfield(L, -2, "hsplit"); 212 | 213 | rose_lua_table_add_field("rose.window"); 214 | lua_pushcfunction(L, (lua_CFunction) rose_window_split_close); 215 | lua_setfield(L, -2, "split_close"); 216 | } 217 | 218 | void rose_window_destroy(RoseWindow *rw) 219 | { 220 | g_object_unref(rw->window); 221 | free(rw); 222 | } 223 | -------------------------------------------------------------------------------- /src/window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef struct { 7 | GtkWindow *window; 8 | GtkStack *stack; 9 | } RoseWindow; 10 | 11 | RoseWindow *rose_window_new(void); 12 | RoseWindow *rose_window_get(void); 13 | void rose_window_destroy(RoseWindow *); 14 | void rose_window_lua_api(lua_State *L); 15 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | ## 📝 TODO 2 | 3 | ### 🚀 Next stable release 4 | 5 | - [ ] - Cleanup lua stack 6 | - [ ] - Implement find function in webview lua api 7 | - [ ] - Enable sandboxing by default 8 | - [ ] - Implement missing webkit and gtk settings to lua api 9 | - [ ] - Rewrite makefile (current is too slow) 10 | 11 | ### 🌠 Future features 12 | 13 | - [ ] - Custom css style (for sites and gtk) and wide lua api for it 14 | - [ ] - Vertical and horizontal tab view 15 | - [ ] - History 16 | - [ ] - Bookmarks 17 | - [ ] - Rofi like prompt 18 | - [ ] - Search suggestions 19 | --------------------------------------------------------------------------------