├── .gitignore ├── .github └── FUNDING.yml ├── config.conf ├── .gitattributes ├── include ├── libsyshud.hpp ├── libsysauth.hpp ├── libsyspower.hpp ├── libsysbar.hpp ├── libsyslock.hpp ├── libsysmenu.hpp └── libsysboard.hpp ├── src ├── config_parser.hpp ├── libs │ ├── syspower.cpp │ ├── syshud.cpp │ ├── sysauth.cpp │ ├── sysbar.cpp │ ├── syslock.cpp │ ├── sysmenu.cpp │ └── sysboard.cpp ├── extras │ ├── clipboard.hpp │ └── clipboard.cpp ├── main.hpp ├── config_parser.cpp └── main.cpp ├── Makefile ├── README.md └── proto └── wlr-data-control-unstable-v1.xml /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: system64 2 | -------------------------------------------------------------------------------- /config.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | load=auth,bar,board,hud,lock,menu,power 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /include/libsyshud.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class syshud {}; 4 | using syshud_create_func = syshud* (*)(const std::map>&); 5 | inline syshud_create_func syshud_create; 6 | inline syshud* syshud_window; 7 | inline std::map> config_syshud; 8 | -------------------------------------------------------------------------------- /include/libsysauth.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class sysauth {}; 4 | using sysauth_create_func = sysauth* (*)(const std::map>&); 5 | inline sysauth_create_func sysauth_create; 6 | inline sysauth* sysauth_window; 7 | inline std::map> config_sysauth; 8 | -------------------------------------------------------------------------------- /include/libsyspower.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class syspower {}; 4 | using syspower_create_func = syspower* (*)(const std::map>&); 5 | inline syspower_create_func syspower_create; 6 | inline syspower* syspower_window; 7 | inline std::map> config_syspower; 8 | -------------------------------------------------------------------------------- /src/config_parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // INI parser 6 | class config_parser { 7 | public: 8 | config_parser(const std::string &filename); 9 | std::string get_value(const std::string §ion, const std::string &key); 10 | std::map> data; 11 | 12 | private: 13 | std::string trim(const std::string &str); 14 | }; 15 | -------------------------------------------------------------------------------- /include/libsysbar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class sysbar {}; 4 | using sysbar_create_func = sysbar* (*)(const std::map>&); 5 | inline sysbar_create_func sysbar_create; 6 | using sysbar_signal_func = void (*)(sysbar*, int); 7 | inline sysbar_signal_func sysbar_signal; 8 | inline sysbar* sysbar_window; 9 | inline std::map> config_sysbar; 10 | -------------------------------------------------------------------------------- /include/libsyslock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class syslock {}; 4 | using syslock_create_func = syslock* (*)(const std::map>&); 5 | inline syslock_create_func syslock_create; 6 | using syslock_lock_func = void (*)(syslock*); 7 | inline syslock_lock_func syslock_lock; 8 | inline syslock* syslock_window; 9 | inline std::map> config_syslock; 10 | -------------------------------------------------------------------------------- /include/libsysmenu.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class sysmenu {}; 4 | using sysmenu_create_func = sysmenu* (*)(const std::map>&); 5 | inline sysmenu_create_func sysmenu_create; 6 | using sysmenu_signal_func = void (*)(sysmenu*, int); 7 | inline sysmenu_signal_func sysmenu_signal; 8 | inline sysmenu* sysmenu_window; 9 | inline std::map> config_sysmenu; 10 | -------------------------------------------------------------------------------- /include/libsysboard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class sysboard {}; 4 | using sysboard_create_func = sysboard* (*)(const std::map>&); 5 | inline sysboard_create_func sysboard_create; 6 | using sysboard_signal_func = void (*)(sysboard*, int); 7 | inline sysboard_signal_func sysboard_signal; 8 | inline sysboard* sysboard_window; 9 | inline std::map> config_sysboard; 10 | -------------------------------------------------------------------------------- /src/libs/syspower.cpp: -------------------------------------------------------------------------------- 1 | #include "../main.hpp" 2 | 3 | void load_libsyspower() { 4 | std::printf("Loading: libsyspower.so\n"); 5 | void* handle = dlopen("libsyspower.so", RTLD_LAZY); 6 | if (!handle) { 7 | std::fprintf(stderr, "Cannot open library: %s\n", dlerror()); 8 | return; 9 | } 10 | 11 | syspower_create = (syspower_create_func)dlsym(handle, "syspower_create"); 12 | 13 | const char* dlsym_error = dlerror(); 14 | if (dlsym_error) { 15 | std::fprintf(stderr, "Cannot load symbols: %s\n", dlsym_error); 16 | dlclose(handle); 17 | return; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/libs/syshud.cpp: -------------------------------------------------------------------------------- 1 | #include "../main.hpp" 2 | 3 | void load_libsyshud() { 4 | std::printf("Loading: libsyshud.so\n"); 5 | void* handle = dlopen("libsyshud.so", RTLD_LAZY); 6 | if (!handle) { 7 | std::fprintf(stderr, "Cannot open library: %s\n", dlerror()); 8 | return; 9 | } 10 | 11 | syshud_create = (syshud_create_func)dlsym(handle, "syshud_create"); 12 | 13 | const char* dlsym_error = dlerror(); 14 | if (dlsym_error) { 15 | std::fprintf(stderr, "Cannot load symbols: %s\n", dlsym_error); 16 | dlclose(handle); 17 | return; 18 | } 19 | 20 | config_syshud = load_config("hud"); 21 | syshud_window = syshud_create(config_syshud); 22 | } 23 | -------------------------------------------------------------------------------- /src/libs/sysauth.cpp: -------------------------------------------------------------------------------- 1 | #include "../main.hpp" 2 | 3 | void load_libsysauth() { 4 | std::printf("Loading: libsysauth.so\n"); 5 | void* handle = dlopen("libsysauth.so", RTLD_LAZY); 6 | if (!handle) { 7 | std::fprintf(stderr, "Cannot open library: %s\n", dlerror()); 8 | return; 9 | } 10 | 11 | sysauth_create = (sysauth_create_func)dlsym(handle, "sysauth_create"); 12 | 13 | const char* dlsym_error = dlerror(); 14 | if (dlsym_error) { 15 | std::fprintf(stderr, "Cannot load symbols: %s\n", dlsym_error); 16 | dlclose(handle); 17 | return; 18 | } 19 | 20 | config_sysauth = load_config("auth"); 21 | sysauth_window = sysauth_create(config_sysauth); 22 | } 23 | -------------------------------------------------------------------------------- /src/extras/clipboard.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "../wlr-data-control-unstable-v1.h" 7 | 8 | class clipboard : public Gtk::Window { 9 | public: 10 | clipboard(); 11 | 12 | zwlr_data_control_manager_v1* control_manager; 13 | 14 | Glib::RefPtr gdk_clipboard; 15 | GdkDisplay* gdk_display; 16 | GdkSeat* gdk_seat; 17 | wl_seat* seat; 18 | wl_display* display; 19 | 20 | void add_item(const std::string&); 21 | 22 | private: 23 | Gtk::Box box_main; 24 | Gtk::Entry entry_search; 25 | Gtk::FlowBox flowbox_main; 26 | }; 27 | -------------------------------------------------------------------------------- /src/libs/sysbar.cpp: -------------------------------------------------------------------------------- 1 | #include "../main.hpp" 2 | 3 | void load_libsysbar() { 4 | std::printf("Loading: libsysbar.so\n"); 5 | 6 | void* handle = dlopen("libsysbar.so", RTLD_LAZY); 7 | if (!handle) { 8 | std::fprintf(stderr, "Cannot open library: %s\n", dlerror()); 9 | return; 10 | } 11 | 12 | sysbar_create = (sysbar_create_func)dlsym(handle, "sysbar_create"); 13 | sysbar_signal = (sysbar_signal_func)dlsym(handle, "sysbar_signal"); 14 | 15 | const char* dlsym_error = dlerror(); 16 | if (dlsym_error) { 17 | std::fprintf(stderr, "Cannot load symbols: %s\n", dlsym_error); 18 | dlclose(handle); 19 | return; 20 | } 21 | 22 | config_sysbar = load_config("bar"); 23 | sysbar_window = sysbar_create(config_sysbar); 24 | } 25 | -------------------------------------------------------------------------------- /src/main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config_parser.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "extras/clipboard.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | void load_libsysauth(); 19 | void load_libsysbar(); 20 | void load_libsysboard(); 21 | void load_libsyshud(); 22 | void load_libsyslock(); 23 | void load_libsysmenu(); 24 | void load_libsyspower(); 25 | 26 | inline clipboard* cl; 27 | 28 | std::map> load_config(const std::string&); 29 | 30 | inline struct config_shell { 31 | std::string load = "auth,bar,board,hud,lock,menu,power"; 32 | } cfg_shell; 33 | -------------------------------------------------------------------------------- /src/libs/syslock.cpp: -------------------------------------------------------------------------------- 1 | #include "../main.hpp" 2 | 3 | void load_libsyslock() { 4 | Glib::Dispatcher* dispatcher_callback = new Glib::Dispatcher(); 5 | 6 | dispatcher_callback->connect([&]() { 7 | syslock_window = syslock_create(config_syslock); 8 | }); 9 | 10 | std::thread([&, dispatcher_callback]() { 11 | std::printf("Loading: libsyslock.so\n"); 12 | void* handle = dlopen("libsyslock.so", RTLD_LAZY); 13 | if (!handle) { 14 | std::fprintf(stderr, "Cannot open library: %s\n", dlerror()); 15 | return; 16 | } 17 | 18 | syslock_create = (syslock_create_func)dlsym(handle, "syslock_create"); 19 | syslock_lock = (syslock_lock_func)dlsym(handle, "syslock_lock"); 20 | 21 | const char* dlsym_error = dlerror(); 22 | if (dlsym_error) { 23 | std::fprintf(stderr, "Cannot load symbols: %s\n", dlsym_error); 24 | dlclose(handle); 25 | return; 26 | } 27 | 28 | config_syslock = load_config("lock"); 29 | dispatcher_callback->emit(); 30 | }).detach(); 31 | } 32 | -------------------------------------------------------------------------------- /src/libs/sysmenu.cpp: -------------------------------------------------------------------------------- 1 | #include "../main.hpp" 2 | 3 | void load_libsysmenu() { 4 | Glib::Dispatcher* dispatcher_callback = new Glib::Dispatcher(); 5 | 6 | dispatcher_callback->connect([&]() { 7 | sysmenu_window = sysmenu_create(config_sysmenu); 8 | }); 9 | 10 | std::thread([&, dispatcher_callback]() { 11 | std::printf("Loading: libsysmenu.so\n"); 12 | void* handle = dlopen("libsysmenu.so", RTLD_LAZY); 13 | if (!handle) { 14 | std::fprintf(stderr, "Cannot open library: %s\n", dlerror()); 15 | return; 16 | } 17 | 18 | sysmenu_create = (sysmenu_create_func)dlsym(handle, "sysmenu_create"); 19 | sysmenu_signal = (sysmenu_signal_func)dlsym(handle, "sysmenu_signal"); 20 | 21 | const char* dlsym_error = dlerror(); 22 | if (dlsym_error) { 23 | std::fprintf(stderr, "Cannot load symbols: %s\n", dlsym_error); 24 | dlclose(handle); 25 | return; 26 | } 27 | 28 | config_sysmenu = load_config("menu"); 29 | dispatcher_callback->emit(); 30 | }).detach(); 31 | } 32 | -------------------------------------------------------------------------------- /src/libs/sysboard.cpp: -------------------------------------------------------------------------------- 1 | #include "../main.hpp" 2 | 3 | void load_libsysboard() { 4 | Glib::Dispatcher* dispatcher_callback = new Glib::Dispatcher(); 5 | 6 | dispatcher_callback->connect([&]() { 7 | sysboard_window = sysboard_create(config_sysboard); 8 | }); 9 | 10 | std::thread([&, dispatcher_callback]() { 11 | std::printf("Loading: libsysboard.so\n"); 12 | void* handle = dlopen("libsysboard.so", RTLD_LAZY); 13 | if (!handle) { 14 | std::fprintf(stderr, "Cannot open library: %s\n", dlerror()); 15 | return; 16 | } 17 | 18 | sysboard_create = (sysboard_create_func)dlsym(handle, "sysboard_create"); 19 | sysboard_signal = (sysboard_signal_func)dlsym(handle, "sysboard_signal"); 20 | 21 | const char* dlsym_error = dlerror(); 22 | if (dlsym_error) { 23 | std::fprintf(stderr, "Cannot load symbols: %s\n", dlsym_error); 24 | dlclose(handle); 25 | return; 26 | } 27 | 28 | config_sysboard = load_config("board"); 29 | dispatcher_callback->emit(); 30 | }).detach(); 31 | } 32 | -------------------------------------------------------------------------------- /src/config_parser.cpp: -------------------------------------------------------------------------------- 1 | #include "config_parser.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | config_parser::config_parser(const std::string &filename) { 7 | std::ifstream file(filename); 8 | std::string line; 9 | std::string current_section; 10 | 11 | if (file.is_open()) { 12 | while (std::getline(file, line)) { 13 | line = trim(line); 14 | 15 | if (line.empty() || line[0] == ';' || line[0] == '#') { 16 | continue; 17 | } 18 | else if (line[0] == '[' && line[line.size() - 1] == ']') { 19 | current_section = line.substr(1, line.size() - 2); 20 | } 21 | else { 22 | size_t delimiter_pos = line.find('='); 23 | if (delimiter_pos != std::string::npos) { 24 | std::string key = trim(line.substr(0, delimiter_pos)); 25 | std::string value = trim(line.substr(delimiter_pos + 1)); 26 | data[current_section][key] = value; 27 | } 28 | } 29 | } 30 | file.close(); 31 | } 32 | else { 33 | std::cerr << "Unable to open file: " << filename << std::endl; 34 | } 35 | } 36 | 37 | std::string config_parser::get_value(const std::string §ion, const std::string &key) { 38 | auto section_iter = data.find(section); 39 | if (section_iter != data.end()) { 40 | auto key_iter = section_iter->second.find(key); 41 | if (key_iter != section_iter->second.end()) { 42 | return key_iter->second; 43 | } 44 | else { 45 | std::cerr << "Key '" << key << "' not found in section '" << section << "'" << std::endl; 46 | } 47 | } 48 | else { 49 | std::cerr << "Section '" << section << "' not found" << std::endl; 50 | } 51 | return "empty"; 52 | } 53 | 54 | std::string config_parser::trim(const std::string &str) { 55 | size_t first = str.find_first_not_of(' '); 56 | if (std::string::npos == first) { 57 | return str; 58 | } 59 | size_t last = str.find_last_not_of(' '); 60 | return str.substr(first, (last - first + 1)); 61 | } 62 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINS = sysshell 2 | PKGS = gtkmm-4.0 gtk4-layer-shell-0 3 | SRCS = $(wildcard src/*.cpp src/libs/*.cpp src/extras/*.cpp) 4 | 5 | PREFIX ?= /usr/local 6 | BINDIR ?= $(PREFIX)/bin 7 | DATADIR ?= $(PREFIX)/share 8 | BUILDDIR = build 9 | 10 | CXXFLAGS += -Os -s -Wall -flto=auto -fno-exceptions 11 | LDFLAGS += -Wl,-O1,--no-as-needed,-z,now,-z,pack-relative-relocs 12 | 13 | CXXFLAGS += $(shell pkg-config --cflags $(PKGS)) 14 | LDFLAGS += $(shell pkg-config --libs $(PKGS)) 15 | 16 | OBJS = $(patsubst src/%.cpp, $(BUILDDIR)/%.o, $(SRCS)) 17 | 18 | PROTOS = $(wildcard proto/*.xml) 19 | PROTO_HDRS = $(patsubst proto/%.xml, src/%.h, $(PROTOS)) 20 | PROTO_SRCS = $(patsubst proto/%.xml, src/%.c, $(PROTOS)) 21 | PROTO_OBJS = $(patsubst src/%.c, $(BUILDDIR)/%.o, $(PROTO_SRCS)) 22 | 23 | JOB_COUNT := $(BINS) $(OBJS) $(PROTO_HDRS) $(PROTO_SRCS) $(PROTO_OBJS) 24 | JOBS_DONE := $(shell ls -l $(JOB_COUNT) 2> /dev/null | wc -l) 25 | 26 | define progress 27 | $(eval JOBS_DONE := $(shell echo $$(($(JOBS_DONE) + 1)))) 28 | @printf "[$(JOBS_DONE)/$(shell echo $(JOB_COUNT) | wc -w)] %s %s\n" $(1) $(2) 29 | endef 30 | 31 | all: $(BINS) 32 | 33 | install: $(BINS) 34 | @echo "Installing..." 35 | @install -D -t $(DESTDIR)$(BINDIR) $(BUILDDIR)/$(BINS) 36 | @install -D -t $(DESTDIR)$(DATADIR)/sys64/shell config.conf 37 | 38 | clean: 39 | @echo "Cleaning up" 40 | @rm -rf $(BUILDDIR) $(PROTO_HDRS) $(PROTO_SRCS) 41 | 42 | $(BINS): $(PROTO_HDRS) $(PROTO_SRCS) $(PROTO_OBJS) $(OBJS) 43 | $(call progress, Linking $@) 44 | @$(CXX) -o $(BUILDDIR)/$@ \ 45 | $(OBJS) \ 46 | $(PROTO_OBJS) \ 47 | $(CXXFLAGS) \ 48 | $(LDFLAGS) -lwayland-client 49 | 50 | $(BUILDDIR)/%.o: src/%.cpp 51 | @mkdir -p $(dir $@) 52 | $(call progress, Compiling $@) 53 | @$(CXX) -c $< -o $@ \ 54 | $(CXXFLAGS) \ 55 | -I include 56 | 57 | $(BUILDDIR)/%.o: src/%.c 58 | @mkdir -p $(dir $@) 59 | $(call progress, Compiling $@) 60 | @$(CC) -c $< -o $@ $(CFLAGS) 61 | 62 | $(PROTO_HDRS): src/%.h : proto/%.xml 63 | $(call progress, Creating $@) 64 | @wayland-scanner client-header $< $@ 65 | 66 | $(PROTO_SRCS): src/%.c : proto/%.xml 67 | $(call progress, Creating $@) 68 | @wayland-scanner public-code $< $@ 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sysshell 2 | sysshell is a generic shell meant to provide a cohesive experience for all.
3 | It's also a personal project so goals and features may cator to my requirements.
4 | 5 | # Features 6 | ## [sysbar](https://github.com/System64fumo/sysbar) 7 | Modular status bar
8 | preview 9 | 10 | ## [sysboard](https://github.com/System64fumo/sysboard) 11 | Simple virtual keyboard
12 | preview 13 | 14 | ## [syshud](https://github.com/System64fumo/syshud) 15 | Simple system status indicator
16 | preview 17 | 18 | ## [syslock](https://github.com/System64fumo/syslock) 19 | Simple screen locker
20 | preview 21 | 22 | ## [sysmenu](https://github.com/System64fumo/sysmenu) 23 | Simple and efficient application launcher
24 | preview 25 | 26 | ## [syspower](https://github.com/System64fumo/syspower) 27 | Simple power menu/shutdown screen
28 | preview 29 | 30 | 31 | # Signals 32 | Use the following signals to perform the corresponding actions.
33 | `pkill -10` sysmenu: Show window
34 | `pkill -12` sysmenu: Hide window
35 | `pkill -34` sysmenu: Toggle window
36 | `pkill -35` syslock: Lock screen
37 | `pkill -36` sysbar: Show window
38 | `pkill -37` sysbar: Hide window
39 | `pkill -38` sysbar: Toggle window
40 | `pkill -39` syspower: Show window
41 | `pkill -40` sysboard: Show window
42 | `pkill -41` sysboard: Hide window
43 | `pkill -42` sysboard: Toggle window
44 | `pkill -43` clipboard: Show window
45 | 46 | 47 | ## Clipboard 48 | Simple clipboard history manager
49 | This is the first exclusive feature, To enable it add `extras=clipboard` to your config.
50 | Please do note!! This feature is still experimental and thus lacking features.
51 | Additionally, Your clipboard gets copied to a temporary file (That gets deleted a few ms later) so it's sort of insecure.
52 | 53 | # Why does this exist? 54 | The sysshell binary exists as a way to reduce resource consumption as launching several application instances tends to consume more memory.
55 | The shell components exist because there wasn't cohesive DE agnostic graphical shell out there that did things the way i wanted.
56 | Also because i wanted to have the control and flexibility of having my own shell.
57 | 58 | # Contribution 59 | This is my most ambitious project yet and i'm unsure if i'm capable of finishing anything i've started.
60 | If you wish for a feature to be added/fixed/worked on please open an issue, I prefer focusing on what annoys me or others first.
61 | Additionally, Any kind of contribution is welcome be it documentation, code, cleanup, etc.
62 | 63 | # Packaging 64 | I package my software on the AUR, However some parts of my software also exists on other repositories maintained by the community.
65 | Maintaining packages for every distribution out there sounds like a lot of work, So if you'd like to feel free to package and maintain any of my projects on your repo of choice.
(Please open an issue so i can keep track of what exists where, I'd like to eventually have a list in the project's repos) 66 | 67 | # GNOME/GTK 68 | Unfortunately the GNOME foundation is not the best.. And their decisions often leaves people divided such as with GTK5 and Libadwaita.
69 | I've attempted to rewrite the shell in QT but quickly found out that it's not a serious alternative to GTK..
70 | If you take issue with GNOME's direction, leadership, politics or GTK and want to avoid it wherever possible i suggest you take a look at [Quickshell](https://quickshell.org/).
71 | 72 | # Future plans 73 | Create a screenshot overlay
74 | Create a polkit/keyring overlay
75 | Optimize existing stuff as much as possible, At the moment the focus is on features
76 | Split/Handle shared code via a common library to reduce duplicate code
77 | Possibly rewrite some code in pure C to reduce complexity and compile time?
78 | 79 | # License 80 | My software is typically licensed under WTFPL but will be replaced by GPL in the near future.
81 | I grant you permission to do practically anything with my software, All i ask in exchange is to be credited.
82 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "main.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | std::map> load_config(const std::string& name) { 8 | std::string config_path; 9 | std::map> config; 10 | std::map> config_usr; 11 | 12 | bool cfg_sys = std::filesystem::exists("/usr/share/sys64/" + name + "/config.conf"); 13 | bool cfg_sys_local = std::filesystem::exists("/usr/local/share/sys64/" + name + "/config.conf"); 14 | bool cfg_usr = std::filesystem::exists(std::string(getenv("HOME")) + "/.config/sys64/" + name + "/config.conf"); 15 | 16 | // Load default config 17 | if (cfg_sys) 18 | config_path = "/usr/share/sys64/" + name + "/config.conf"; 19 | else if (cfg_sys_local) 20 | config_path = "/usr/local/share/sys64/" + name + "/config.conf"; 21 | else 22 | std::fprintf(stderr, "No default config found, Things will get funky!\n"); 23 | 24 | config = config_parser(config_path).data; 25 | 26 | // Load user config 27 | if (cfg_usr) 28 | config_path = std::string(getenv("HOME")) + "/.config/sys64/" + name + "/config.conf"; 29 | else 30 | std::fprintf(stderr, "No user config found\n"); 31 | 32 | config_usr = config_parser(config_path).data; 33 | 34 | // Merge configs 35 | for (const auto& [key, nested_map] : config_usr) 36 | config[key] = nested_map; 37 | 38 | // Sanity check 39 | if (!(cfg_sys || cfg_sys_local || cfg_usr)) { 40 | std::fprintf(stderr, "No config available, Something ain't right here."); 41 | return config; 42 | } 43 | return config; 44 | } 45 | 46 | void handle_signal(int signum) { 47 | // TODO: Prevent sysmenu from working when syslock is locked 48 | if (signum == 36) 49 | sysbar_signal(sysbar_window, 10); 50 | else if (signum == 37) 51 | sysbar_signal(sysbar_window, 12); 52 | else if (signum == 38) 53 | sysbar_signal(sysbar_window, 34); 54 | else if (signum == 10 || signum == 12 || signum == 34) 55 | sysmenu_signal(sysmenu_window, signum); 56 | else if (signum == 35) 57 | syslock_lock(syslock_window); 58 | else if (signum == 39) { 59 | // TODO: Show other windows 60 | // TODO: Only show if another instance is not already running 61 | syspower_window = syspower_create(load_config("power")); 62 | } 63 | else if (signum == 40) { 64 | sysboard_signal(sysboard_window, 10); 65 | } 66 | else if (signum == 41) { 67 | sysboard_signal(sysboard_window, 12); 68 | } 69 | else if (signum == 42) { 70 | sysboard_signal(sysboard_window, 34); 71 | } 72 | else if (signum == 43) { 73 | cl->show(); 74 | } 75 | } 76 | 77 | int main() { 78 | auto app = Gtk::Application::create("funky.sys64.sysshell"); 79 | app->hold(); 80 | 81 | // This is required for linking to work properly 82 | bool supported = gtk_layer_is_supported(); 83 | if (!supported) { 84 | std::fprintf(stderr, "I've got a sneaking suspision that your compositor doesn't support gtk layer shell..\n"); 85 | return 1; 86 | } 87 | 88 | // Parse the config 89 | std::string config_path; 90 | if (std::filesystem::exists(std::string(getenv("HOME")) + "/.config/sys64/shell/config.conf")) 91 | config_path = std::string(getenv("HOME")) + "/.config/sys64/shell/config.conf"; 92 | else if (std::filesystem::exists("/usr/share/sys64/shell/config.conf")) 93 | config_path = "/usr/share/sys64/shell/config.conf"; 94 | else 95 | config_path = "/usr/local/share/sys64/shell/config.conf"; 96 | 97 | config_parser config(config_path); 98 | 99 | std::string libraries = config.get_value("main", "load"); 100 | if (libraries != "empty") 101 | cfg_shell.load = libraries; 102 | 103 | // Load libraries 104 | std::istringstream iss(cfg_shell.load); 105 | std::string library; 106 | while (std::getline(iss, library, ',')) { 107 | if (library == "auth") 108 | load_libsysauth(); 109 | else if (library == "bar") 110 | load_libsysbar(); 111 | else if (library == "board") 112 | load_libsysboard(); 113 | else if (library == "hud") 114 | load_libsyshud(); 115 | else if (library == "lock") 116 | load_libsyslock(); 117 | else if (library == "menu") 118 | load_libsysmenu(); 119 | else if (library == "power") 120 | load_libsyspower(); 121 | else { 122 | std::fprintf(stderr, "Cannot load \"%s\"\n", library.c_str()); 123 | } 124 | } 125 | 126 | std::string extras = config.get_value("main", "extras"); 127 | 128 | // Load libraries 129 | std::istringstream iss_extras(extras); 130 | std::string feature; 131 | while (std::getline(iss_extras, feature, ',')) { 132 | if (feature == "clipboard") { 133 | std::printf("Loading clipboard feature\n"); 134 | cl = Gtk::make_managed(); 135 | } 136 | else { 137 | std::fprintf(stderr, "Feature not found \"%s\"\n", feature.c_str()); 138 | } 139 | } 140 | 141 | // Catch signals 142 | // TODO: Add a config to assign custom signals to each action 143 | signal(SIGRTMIN+2, handle_signal); // sysbar: show 144 | signal(SIGRTMIN+3, handle_signal); // sysbar: hide 145 | signal(SIGRTMIN+4, handle_signal); // sysbar: toggle 146 | 147 | signal(SIGRTMIN+6, handle_signal); // sysboard: show 148 | signal(SIGRTMIN+7, handle_signal); // sysboard: hide 149 | signal(SIGRTMIN+8, handle_signal); // sysboard: toggle 150 | 151 | signal(SIGUSR1, handle_signal); // sysmenu: show 152 | signal(SIGUSR2, handle_signal); // sysmenu: hide 153 | signal(SIGRTMIN, handle_signal); // sysmenu: toggle 154 | 155 | signal(SIGRTMIN+1, handle_signal); // syslock: lock 156 | 157 | signal(SIGRTMIN+5, handle_signal); // syspower: show 158 | 159 | signal(SIGRTMIN+9, handle_signal); // clipboard: show 160 | 161 | return app->run(); 162 | } 163 | -------------------------------------------------------------------------------- /src/extras/clipboard.cpp: -------------------------------------------------------------------------------- 1 | #include "clipboard.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static void data_control_offer(void* data, struct zwlr_data_control_offer_v1* offer, const char* mime_type) { 13 | auto self = static_cast(data); 14 | //std::printf("Available mime types: %s\n", mime_type); 15 | 16 | std::string mime = mime_type; 17 | std::string clipboard_path = std::string(getenv("XDG_RUNTIME_DIR")) + "/sysshell_clipboard.tmp"; 18 | 19 | // Skip unsupported mime types 20 | if (!(mime == "text/plain" || mime == "STRING" || mime == "TEXT" || mime == "x-special/gnome-copied-files")) 21 | return; 22 | 23 | // Write clipboard data to a temporary file 24 | FILE *file_ptr = fopen(clipboard_path.c_str(), "w+"); 25 | fclose(file_ptr); 26 | int fd = open(clipboard_path.c_str(), O_WRONLY); 27 | 28 | // TODO: Not everything is a plain text string, Add support for other mime types 29 | zwlr_data_control_offer_v1_receive(offer, mime_type, fd); 30 | zwlr_data_control_offer_v1_destroy(offer); 31 | close(fd); 32 | 33 | // Slight delay has to happen between writing and reading the file 34 | // Again this is annoying and i don't want this 35 | // But a temporary solution is better than no solution 36 | std::thread([self, clipboard_path]() { 37 | usleep(100 * 1000); 38 | std::ifstream file(clipboard_path); 39 | std::string contents((std::istreambuf_iterator(file)), 40 | std::istreambuf_iterator()); 41 | remove(clipboard_path.c_str()); 42 | 43 | Glib::signal_idle().connect([&, self, contents]() { 44 | self->add_item(contents); 45 | return false; 46 | }); 47 | }).detach(); 48 | } 49 | 50 | struct zwlr_data_control_offer_v1_listener data_control_offer_listener = { 51 | data_control_offer 52 | }; 53 | 54 | static void data_control_device_offer(void* data, struct zwlr_data_control_device_v1* device, struct zwlr_data_control_offer_v1* offer) { 55 | if (!offer) 56 | return; 57 | zwlr_data_control_offer_v1_add_listener(offer, &data_control_offer_listener, data); 58 | } 59 | 60 | static void data_control_device_selection(void* data, struct zwlr_data_control_device_v1* device, struct zwlr_data_control_offer_v1* offer) { 61 | if (!offer) 62 | return; 63 | zwlr_data_control_offer_v1_destroy(offer); 64 | } 65 | 66 | static void data_control_device_primary_selection(void* data, struct zwlr_data_control_device_v1* device, struct zwlr_data_control_offer_v1* offer) { 67 | if (!offer) 68 | return; 69 | zwlr_data_control_offer_v1_destroy(offer); 70 | } 71 | 72 | static struct zwlr_data_control_device_v1_listener data_control_device_listener = { 73 | .data_offer = data_control_device_offer, 74 | .selection = data_control_device_selection, 75 | .primary_selection = data_control_device_primary_selection 76 | }; 77 | 78 | static void registry_handler(void* data, struct wl_registry* registry, 79 | uint32_t id, const char* interface, uint32_t version) { 80 | 81 | auto self = static_cast(data); 82 | 83 | if (strcmp(interface, zwlr_data_control_manager_v1_interface.name) == 0) { 84 | self->control_manager = (zwlr_data_control_manager_v1*) 85 | wl_registry_bind(registry, id, 86 | &zwlr_data_control_manager_v1_interface, 1u); 87 | zwlr_data_control_device_v1* device = 88 | zwlr_data_control_manager_v1_get_data_device( 89 | self->control_manager, 90 | self->seat); 91 | zwlr_data_control_device_v1_add_listener(device, &data_control_device_listener, self); 92 | 93 | wl_display_roundtrip(self->display); 94 | } 95 | } 96 | 97 | static wl_registry_listener registry_listener = { 98 | ®istry_handler 99 | }; 100 | 101 | clipboard::clipboard() : gdk_clipboard(get_clipboard()), box_main(Gtk::Orientation::VERTICAL) { 102 | // TODO: Add positioning control (Ideally place under the cursor or near the text entry field) 103 | 104 | // Layer shell stuff 105 | gtk_layer_init_for_window(gobj()); 106 | gtk_layer_set_namespace(gobj(), "sysclip"); 107 | gtk_layer_set_layer(gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY); 108 | gtk_layer_set_keyboard_mode(gobj(), GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND); 109 | 110 | // UI Setup 111 | set_name("sysclip"); 112 | set_hide_on_close(true); 113 | set_default_size(300, 400); 114 | 115 | set_child(box_main); 116 | //box_main.append(entry_search); // TODO: Work on this later 117 | box_main.append(flowbox_main); 118 | flowbox_main.set_max_children_per_line(1); 119 | flowbox_main.set_selection_mode(Gtk::SelectionMode::NONE); 120 | 121 | flowbox_main.signal_child_activated().connect([&](Gtk::FlowBoxChild* child) { 122 | auto box_hist = dynamic_cast(child->get_child()); 123 | auto label_hist = dynamic_cast(box_hist->get_children()[0]); 124 | gdk_clipboard->set_text(label_hist->get_text()); 125 | }); 126 | 127 | auto controller = Gtk::EventControllerKey::create(); 128 | controller->signal_key_pressed().connect([&](const guint &keyval, const guint &keycode, const Gdk::ModifierType &state) { 129 | if (keyval == 65307) // Escape key 130 | hide(); 131 | 132 | return false; 133 | }, true); 134 | add_controller(controller); 135 | 136 | // Clipboard monitor setup 137 | gdk_display = gdk_display_get_default(); 138 | gdk_seat = gdk_display_get_default_seat(gdk_display); 139 | seat = gdk_wayland_seat_get_wl_seat(gdk_seat); 140 | auto g_display = gdk_wayland_display_get_wl_display(gdk_display); 141 | display = wl_display_connect(NULL); 142 | auto registry = wl_display_get_registry(g_display); 143 | wl_registry_add_listener(registry, ®istry_listener, this); 144 | } 145 | 146 | void clipboard::add_item(const std::string& value) { 147 | auto flowbox_child_hist = Gtk::make_managed(); 148 | auto box_hist = Gtk::make_managed(); 149 | auto label_hist = Gtk::make_managed(value); 150 | auto button_hist = Gtk::make_managed("x"); 151 | 152 | label_hist->set_hexpand(true); 153 | label_hist->set_xalign(0); 154 | 155 | button_hist->signal_clicked().connect([&, flowbox_child_hist]() { 156 | flowbox_main.remove(*flowbox_child_hist); 157 | }); 158 | 159 | flowbox_child_hist->set_child(*box_hist); 160 | box_hist->append(*label_hist); 161 | box_hist->append(*button_hist); 162 | 163 | // TODO: This is terrible, Maybe re-sort instead of re-create? 164 | 165 | // Remove existing item 166 | for (const auto& child : flowbox_main.get_children()) { 167 | auto fbox_child = dynamic_cast(child); 168 | auto fbc_box_hist = dynamic_cast(fbox_child->get_child()); 169 | auto fbc_label_hist = dynamic_cast(fbc_box_hist->get_children()[0]); 170 | if (fbc_label_hist->get_text() == label_hist->get_text()) { 171 | flowbox_main.remove(*fbox_child); 172 | } 173 | } 174 | 175 | flowbox_main.prepend(*flowbox_child_hist); 176 | 177 | // TODO: Add config option to set this 178 | if (flowbox_main.get_children().size() > 10) 179 | flowbox_main.remove(*flowbox_main.get_children()[0]); 180 | } 181 | -------------------------------------------------------------------------------- /proto/wlr-data-control-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2018 Simon Ser 5 | Copyright © 2019 Ivan Molodetskikh 6 | 7 | Permission to use, copy, modify, distribute, and sell this 8 | software and its documentation for any purpose is hereby granted 9 | without fee, provided that the above copyright notice appear in 10 | all copies and that both that copyright notice and this permission 11 | notice appear in supporting documentation, and that the name of 12 | the copyright holders not be used in advertising or publicity 13 | pertaining to distribution of the software without specific, 14 | written prior permission. The copyright holders make no 15 | representations about the suitability of this software for any 16 | purpose. It is provided "as is" without express or implied 17 | warranty. 18 | 19 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 20 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 21 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 24 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 25 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 26 | THIS SOFTWARE. 27 | 28 | 29 | 30 | This protocol allows a privileged client to control data devices. In 31 | particular, the client will be able to manage the current selection and take 32 | the role of a clipboard manager. 33 | 34 | Warning! The protocol described in this file is experimental and 35 | backward incompatible changes may be made. Backward compatible changes 36 | may be added together with the corresponding interface version bump. 37 | Backward incompatible changes are done by bumping the version number in 38 | the protocol and interface names and resetting the interface version. 39 | Once the protocol is to be declared stable, the 'z' prefix and the 40 | version number in the protocol and interface names are removed and the 41 | interface version number is reset. 42 | 43 | 44 | 45 | 46 | This interface is a manager that allows creating per-seat data device 47 | controls. 48 | 49 | 50 | 51 | 52 | Create a new data source. 53 | 54 | 56 | 57 | 58 | 59 | 60 | Create a data device that can be used to manage a seat's selection. 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | All objects created by the manager will still remain valid, until their 69 | appropriate destroy request has been called. 70 | 71 | 72 | 73 | 74 | 75 | 76 | This interface allows a client to manage a seat's selection. 77 | 78 | When the seat is destroyed, this object becomes inert. 79 | 80 | 81 | 82 | 83 | This request asks the compositor to set the selection to the data from 84 | the source on behalf of the client. 85 | 86 | The given source may not be used in any further set_selection or 87 | set_primary_selection requests. Attempting to use a previously used 88 | source is a protocol error. 89 | 90 | To unset the selection, set the source to NULL. 91 | 92 | 94 | 95 | 96 | 97 | 98 | Destroys the data device object. 99 | 100 | 101 | 102 | 103 | 104 | The data_offer event introduces a new wlr_data_control_offer object, 105 | which will subsequently be used in either the 106 | wlr_data_control_device.selection event (for the regular clipboard 107 | selections) or the wlr_data_control_device.primary_selection event (for 108 | the primary clipboard selections). Immediately following the 109 | wlr_data_control_device.data_offer event, the new data_offer object 110 | will send out wlr_data_control_offer.offer events to describe the MIME 111 | types it offers. 112 | 113 | 114 | 115 | 116 | 117 | 118 | The selection event is sent out to notify the client of a new 119 | wlr_data_control_offer for the selection for this device. The 120 | wlr_data_control_device.data_offer and the wlr_data_control_offer.offer 121 | events are sent out immediately before this event to introduce the data 122 | offer object. The selection event is sent to a client when a new 123 | selection is set. The wlr_data_control_offer is valid until a new 124 | wlr_data_control_offer or NULL is received. The client must destroy the 125 | previous selection wlr_data_control_offer, if any, upon receiving this 126 | event. 127 | 128 | The first selection event is sent upon binding the 129 | wlr_data_control_device object. 130 | 131 | 133 | 134 | 135 | 136 | 137 | This data control object is no longer valid and should be destroyed by 138 | the client. 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | The primary_selection event is sent out to notify the client of a new 147 | wlr_data_control_offer for the primary selection for this device. The 148 | wlr_data_control_device.data_offer and the wlr_data_control_offer.offer 149 | events are sent out immediately before this event to introduce the data 150 | offer object. The primary_selection event is sent to a client when a 151 | new primary selection is set. The wlr_data_control_offer is valid until 152 | a new wlr_data_control_offer or NULL is received. The client must 153 | destroy the previous primary selection wlr_data_control_offer, if any, 154 | upon receiving this event. 155 | 156 | If the compositor supports primary selection, the first 157 | primary_selection event is sent upon binding the 158 | wlr_data_control_device object. 159 | 160 | 162 | 163 | 164 | 165 | 166 | This request asks the compositor to set the primary selection to the 167 | data from the source on behalf of the client. 168 | 169 | The given source may not be used in any further set_selection or 170 | set_primary_selection requests. Attempting to use a previously used 171 | source is a protocol error. 172 | 173 | To unset the primary selection, set the source to NULL. 174 | 175 | The compositor will ignore this request if it does not support primary 176 | selection. 177 | 178 | 180 | 181 | 182 | 183 | 185 | 186 | 187 | 188 | 189 | 190 | The wlr_data_control_source object is the source side of a 191 | wlr_data_control_offer. It is created by the source client in a data 192 | transfer and provides a way to describe the offered data and a way to 193 | respond to requests to transfer the data. 194 | 195 | 196 | 197 | 199 | 200 | 201 | 202 | 203 | This request adds a MIME type to the set of MIME types advertised to 204 | targets. Can be called several times to offer multiple types. 205 | 206 | Calling this after wlr_data_control_device.set_selection is a protocol 207 | error. 208 | 209 | 211 | 212 | 213 | 214 | 215 | Destroys the data source object. 216 | 217 | 218 | 219 | 220 | 221 | Request for data from the client. Send the data as the specified MIME 222 | type over the passed file descriptor, then close it. 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | This data source is no longer valid. The data source has been replaced 231 | by another data source. 232 | 233 | The client should clean up and destroy this data source. 234 | 235 | 236 | 237 | 238 | 239 | 240 | A wlr_data_control_offer represents a piece of data offered for transfer 241 | by another client (the source client). The offer describes the different 242 | MIME types that the data can be converted to and provides the mechanism 243 | for transferring the data directly from the source client. 244 | 245 | 246 | 247 | 248 | To transfer the offered data, the client issues this request and 249 | indicates the MIME type it wants to receive. The transfer happens 250 | through the passed file descriptor (typically created with the pipe 251 | system call). The source client writes the data in the MIME type 252 | representation requested and then closes the file descriptor. 253 | 254 | The receiving client reads from the read end of the pipe until EOF and 255 | then closes its end, at which point the transfer is complete. 256 | 257 | This request may happen multiple times for different MIME types. 258 | 259 | 261 | 262 | 263 | 264 | 265 | 266 | Destroys the data offer object. 267 | 268 | 269 | 270 | 271 | 272 | Sent immediately after creating the wlr_data_control_offer object. 273 | One event per offered MIME type. 274 | 275 | 276 | 277 | 278 | 279 | 280 | --------------------------------------------------------------------------------