Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manage installed plugins from remote (and local) sources #1

Merged
merged 6 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
obj/
*.so
compile_flags.txt
23 changes: 16 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@
PLUGIN_NAME=hyprload

SOURCE_FILES=$(wildcard src/*.cpp)
OBJECT_DIR=obj

INSTALL_PATH=${HOME}/.local/share/hyprload/

COMPILE_FLAGS=-g -fPIC --no-gnu-unique -I "/usr/include/pixman-1" -I "/usr/include/libdrm" -I "${HYPRLAND_HEADERS}" -Iinclude -std=c++23
LINK_FLAGS=-shared

.PHONY: clean clangd

all: check_env $(PLUGIN_NAME).so

install: all
mkdir -p $(INSTALL_PATH)/plugins/bin
mkdir -p $(INSTALL_PATH)plugins/bin
cp $(PLUGIN_NAME).sh $(INSTALL_PATH)
@if [ -f "$(INSTALL_PATH)/$(PLUGIN_NAME).so" ]; then\
cp $(PLUGIN_NAME).so "$(INSTALL_PATH)/$(PLUGIN_NAME).so.update";\
@if [ -f "$(INSTALL_PATH)$(PLUGIN_NAME).so" ]; then\
cp $(PLUGIN_NAME).so "$(INSTALL_PATH)$(PLUGIN_NAME).so.update";\
else\
cp $(PLUGIN_NAME).so $(INSTALL_PATH);\
fi
Expand All @@ -26,11 +30,16 @@ ifndef HYPRLAND_HEADERS
$(error HYPRLAND_HEADERS is undefined! Please set it to the path to the root of the configured Hyprland repo)
endif

$(PLUGIN_NAME).so: $(SOURCE_FILES) $(INCLUDE_FILES)
g++ -shared -fPIC --no-gnu-unique $(SOURCE_FILES) -o $(PLUGIN_NAME).so -g -I "/usr/include/pixman-1" -I "/usr/include/libdrm" -I "${HYPRLAND_HEADERS}" -Iinclude -std=c++23
$(OBJECT_DIR)/%.o: src/%.cpp
mkdir -p $(OBJECT_DIR)
g++ -c -o $@ $< $(COMPILE_FLAGS)

$(PLUGIN_NAME).so: $(addprefix $(OBJECT_DIR)/, $(notdir $(SOURCE_FILES:.cpp=.o)))
g++ $(LINK_FLAGS) -o $@ $^ $(COMPILE_FLAGS)

clean:
rm $(PLUGIN_NAME).so
rm -rf $(OBJECT_DIR)
rm -f $(PLUGIN_NAME).so

clangd:
printf "%b" "-I/usr/include/pixman-1\n-I/usr/include/libdrm\n-I${HYPRLAND_HEADERS}\n-Iinclude\n-std=c++2b" > compile_flags.txt
printf "%b" "-I/usr/include/pixman-1\n-I/usr/include/libdrm\n-I${HYPRLAND_HEADERS}\n-Iinclude\n-std=c++2b\n-Wall\n-Wextra" > compile_flags.txt
49 changes: 42 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Hyprload
A (WIP) minimal [Hyprland](https://github.com/hyprwm/Hyprland) plugin manager
A (WIP) [Hyprland](https://github.com/hyprwm/Hyprland) plugin manager

# Features
- [x] Loading plugins
- [x] Reloading plugins
- [ ] Installing and updating plugins automatically
- [ ] A unified plugin manifest format
- [ ] Installing from git
- [x] Installing and updating plugins automatically
- [x] A unified plugin manifest format
- [x] Installing from git
- [x] Installing from local
- [ ] Installing from a url
- [ ] Self-hosting

# Installing
Since Hyprland plugins don't have ABI guarantees, you *should* download the Hyprland source and compile it if you plan to use plugins.
Expand All @@ -23,7 +25,40 @@ The guide on compiling and installing Hyprland manually is on the [wiki](http://
3. Add this to your config to initialize `hyprload`
- `exec-once=$HOME/.local/share/hyprload/hyprload.sh`

# Usage
Put plugin builds you'd like to use into the `~/.local/share/hyprload/plugins/bin` directory. `hyprload` will load them automatically.
# Setup
1. Before asking for plugins, should add the path to the Hyprland headers in the Hyprland config. [Config variables](#configuration)
2. To have hyprload manage your plugin installation, create a `hyprload.toml` file (by default, next to your `hyprland.conf` config: `~/.config/hypr/hyprload.toml`
3. Fill it out with the plugins you want. It consists of one array, named plugins, in which you can provide the installation source in various ways. Example:
```toml
plugins = [
# Installs the plugin from https://github.com/Duckonaut/split-monitor-workspaces
"Duckonaut/split-monitor-workspaces",
# A more explicit definition of the git install
{ git = "https://github.com/Duckonaut/split-monitor-workspaces", branch = "main", name = "split-monitor-workspaces" },
# Installs the same plugin from a local folder
{ local = "/home/duckonaut/repos/split-monitor-workspaces" },
]
```
4. Add keybinds to the `hyprload` dispatcher in your `hyprland.conf` for the functions you want.
- Possible values:
- `load`: Loads all the plugins
- `clear`: Unloads all the plugins
- `reload`: Unloads then reloads all the plugins
- `overlay`: Toggles an overlay showing your actively loaded plugins
- `install`: Installs the required plugins from `hyprload.toml`
- `update`: Updates the required plugins from `hyprload.toml`
- Example:
```
bind=SUPERSHIFT,R,hyprload,reload
bind=SUPERSHIFT,U,hyprload,update
bind=SUPERSHIFT,O,hyprload,overlay
```

Hyprload keeps "sessions" of plugins, meaning that changing the plugins in `bin` (for example, updating them) does not require a full restart of Hyprload. The original unload will be called, the old plugin will be discarded, the new plugin copy will be loaded.
## Configuration
The configuration of hyprload behavior is done in `hyprland.conf`, like a normal plugin
| Name | Type | Default | Description |
|-------------------------------------------|-----------|-------------------------------|---------------------------------------------------|
| `plugin:hyprload:quiet` | bool | false | Whether to hide the notifications |
| `plugin:hyprload:debug` | bool | false | Whether to hide extra-special debug notifications |
| `plugin:hyprload:config` | string | `~/.config/hypr/hyprload.toml`| The path to your plugin requirements file |
| `plugin:hyprload:hyprload_headers` | string | `empty` | The path to your hyprland local source |
31 changes: 26 additions & 5 deletions include/Hyprload.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#pragma once
#include "HyprloadPlugin.hpp"
#include <memory>
#include <mutex>
#include <variant>
#define WLR_USE_UNSTABLE
#include "HyprloadOverlay.hpp"
#include "types.hpp"
Expand All @@ -17,7 +21,6 @@ namespace hyprload {
const CColor s_debugColor = {0x98 / 255.0f, 0x5f / 255.0f, 0xdd / 255.0f, 1.0f};

const std::string c_pluginRoot = "plugin:hyprload:root";
const std::string c_pluginConfig = "plugin:hyprload:config";
const std::string c_hyprlandHeaders = "plugin:hyprload:hyprland_headers";
const std::string c_pluginQuiet = "plugin:hyprload:quiet";
const std::string c_pluginDebug = "plugin:hyprload:debug";
Expand All @@ -39,11 +42,26 @@ namespace hyprload {

void tryCleanupPreviousSessions();

class Hyprload {
class BuildProcessDescriptor final {
public:
BuildProcessDescriptor(std::string&& name, std::shared_ptr<hyprload::plugin::PluginSource> source, const std::filesystem::path& hyprlandHeadersPath);

std::string m_sName;
std::shared_ptr<hyprload::plugin::PluginSource> m_pSource;
std::filesystem::path m_sHyprlandHeadersPath;

std::mutex m_mMutex;
std::optional<hyprload::Result<std::monostate, std::string>> m_rResult;
};

class Hyprload final {
public:
Hyprload();

void dispatch(const std::string& command);
void handleTick();

void installPlugins();
void updatePlugins();

void loadPlugins();
void reloadPlugins();
Expand All @@ -57,8 +75,8 @@ namespace hyprload {
// Cleanup specific to *this* plugin
void cleanupPlugin();

const std::vector<std::string>& getLoadedPlugins();
bool isPluginLoaded(const std::string& name);
const std::vector<std::string>& getLoadedPlugins() const;
bool isPluginLoaded(const std::string& name) const;

private:
std::optional<std::filesystem::path> getSessionBinariesPath();
Expand All @@ -68,6 +86,9 @@ namespace hyprload {
std::vector<std::string> m_vPlugins;
std::optional<std::string> m_sSessionGuid;
std::optional<flock_t> m_iSessionLock;

bool m_bIsBuilding = false;
std::vector<std::shared_ptr<BuildProcessDescriptor>> m_vBuildProcesses;
};

inline std::unique_ptr<Hyprload> g_pHyprload;
Expand Down
33 changes: 33 additions & 0 deletions include/HyprloadConfig.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once
#include "globals.hpp"
#include "toml/toml.hpp"
#include "HyprloadPlugin.hpp"

#include <string>
#include <filesystem>
#include <variant>
#include <vector>

namespace hyprload::config {
const std::string c_pluginConfig = "plugin:hyprload:config";

std::filesystem::path getConfigPath();


class HyprloadConfig {
public:
HyprloadConfig();

void reloadConfig();
const toml::table& getConfig() const;
const std::vector<hyprload::plugin::PluginRequirement>& getPlugins() const;

private:
void parseConfig();

std::unique_ptr<toml::table> m_pConfig;
std::vector<hyprload::plugin::PluginRequirement> m_vPluginsWanted;
};

inline std::unique_ptr<HyprloadConfig> g_pHyprloadConfig;
}
125 changes: 125 additions & 0 deletions include/HyprloadPlugin.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#pragma once
#include "globals.hpp"
#include "toml/toml.hpp"
#include "types.hpp"

#include <memory>
#include <string>
#include <filesystem>
#include <variant>
#include <vector>

namespace hyprload::plugin {
class PluginManifest {
public:
PluginManifest(std::string&& name, const toml::table& manifest);

const std::string& getName() const;
const std::vector<std::string>& getAuthors() const;
const std::string& getVersion() const;
const std::string& getDescription() const;

const std::filesystem::path& getBinaryOutputPath() const;
const std::vector<std::string>& getBuildSteps() const;

private:
std::string m_sName;
std::vector<std::string> m_sAuthors;
std::string m_sVersion;
std::string m_sDescription;

std::filesystem::path m_pBinaryOutputPath;
std::vector<std::string> m_sBuildSteps;
};

class HyprloadManifest {
public:
HyprloadManifest(const toml::table& manifest);

const std::vector<PluginManifest>& getPlugins() const;

private:
std::vector<PluginManifest> m_vPlugins;
};

class PluginSource {
public:
virtual ~PluginSource() = default;

[[nodiscard]] virtual hyprload::Result<std::monostate, std::string> installSource() = 0;
virtual bool isSourceAvailable() = 0;
virtual bool isUpToDate() = 0;
virtual bool providesPlugin(const std::string& name) const = 0;

[[nodiscard]] virtual hyprload::Result<std::monostate, std::string> update(const std::string& name, const std::filesystem::path& hyprlandHeaders) = 0;
[[nodiscard]] virtual hyprload::Result<std::monostate, std::string> build(const std::string& name, const std::filesystem::path& hyprlandHeaders) = 0;
[[nodiscard]] virtual hyprload::Result<std::monostate, std::string> install(const std::string& name, const std::filesystem::path& hyprlandHeaders) = 0;

bool operator==(const PluginSource& other) const;
protected:
virtual bool isEquivalent(const PluginSource& other) const = 0;
};

class GitPluginSource : public PluginSource {
public:
GitPluginSource(std::string&& url, std::string&& branch);

hyprload::Result<std::monostate, std::string> installSource() override;
bool isSourceAvailable() override;
bool isUpToDate() override;
bool providesPlugin(const std::string& name) const override;

hyprload::Result<std::monostate, std::string> update(const std::string& name, const std::filesystem::path& hyprlandHeaders) override;
hyprload::Result<std::monostate, std::string> build(const std::string& name, const std::filesystem::path& hyprlandHeaders) override;
hyprload::Result<std::monostate, std::string> install(const std::string& name, const std::filesystem::path& hyprlandHeaders) override;

protected:
bool isEquivalent(const PluginSource& other) const override;

private:
std::string m_sUrl;
std::string m_sBranch;
std::filesystem::path m_pSourcePath;
};

class LocalPluginSource : public PluginSource {
public:
LocalPluginSource(std::filesystem::path&& source);

hyprload::Result<std::monostate, std::string> installSource() override;
bool isSourceAvailable() override;
bool isUpToDate() override;
bool providesPlugin(const std::string& name) const override;

hyprload::Result<std::monostate, std::string> update(const std::string& name, const std::filesystem::path& hyprlandHeaders) override;
hyprload::Result<std::monostate, std::string> build(const std::string& name, const std::filesystem::path& hyprlandHeaders) override;
hyprload::Result<std::monostate, std::string> install(const std::string& name, const std::filesystem::path& hyprlandHeaders) override;

protected:
bool isEquivalent(const PluginSource& other) const override;

private:
std::filesystem::path m_pSourcePath;
};

class PluginRequirement {
public:
PluginRequirement(const toml::table& plugin);
PluginRequirement(const std::string& plugin);

const std::string& getName() const;
const std::filesystem::path& getBinaryPath() const;

std::shared_ptr<PluginSource> getSource() const;

bool isInstalled() const;

private:

std::string m_sName;
std::shared_ptr<PluginSource> m_pSource;
std::filesystem::path m_pBinaryPath;
};

inline std::vector<std::shared_ptr<PluginSource>> g_vPluginSources;
}
Loading