├── .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 | 
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 |
--------------------------------------------------------------------------------