mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-11-21 14:15:58 +01:00
portal: Added back screenshot functionality (#127)
This commit is contained in:
parent
081b36add9
commit
b2fc111096
7 changed files with 271 additions and 5 deletions
|
@ -1,4 +1,4 @@
|
|||
[portal]
|
||||
DBusName=org.freedesktop.impl.portal.desktop.hyprland
|
||||
Interfaces=org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.GlobalShortcuts;
|
||||
Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.GlobalShortcuts;
|
||||
UseIn=wlroots;Hyprland;sway;Wayfire;river;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "PortalManager.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "../helpers/MiscFunctions.hpp"
|
||||
|
||||
#include <protocols/hyprland-global-shortcuts-v1-protocol.h>
|
||||
#include <protocols/hyprland-toplevel-export-v1-protocol.h>
|
||||
|
@ -292,6 +293,20 @@ void CPortalManager::init() {
|
|||
else if (m_sWaylandConnection.hyprlandToplevelMgr)
|
||||
m_sPortals.screencopy->appendToplevelExport(m_sWaylandConnection.hyprlandToplevelMgr);
|
||||
|
||||
if (!inShellPath("grim"))
|
||||
Debug::log(WARN, "grim not found. Screenshots will not work.");
|
||||
else {
|
||||
m_sPortals.screenshot = std::make_unique<CScreenshotPortal>();
|
||||
|
||||
if (!inShellPath("slurp"))
|
||||
Debug::log(WARN, "slurp not found. You won't be able to select a region when screenshotting.");
|
||||
|
||||
if (!inShellPath("slurp") && !inShellPath("hyprpicker"))
|
||||
Debug::log(WARN, "Neither slurp nor hyprpicker found. You won't be able to pick colors.");
|
||||
else if (!inShellPath("hyprpicker"))
|
||||
Debug::log(INFO, "hyprpicker not found. We suggest to use hyprpicker for color picking to be less meh.");
|
||||
}
|
||||
|
||||
wl_display_roundtrip(m_sWaylandConnection.display);
|
||||
|
||||
startEventLoop();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <wayland-client.h>
|
||||
|
||||
#include "../portals/Screencopy.hpp"
|
||||
#include "../portals/Screenshot.hpp"
|
||||
#include "../portals/GlobalShortcuts.hpp"
|
||||
#include "../helpers/Timer.hpp"
|
||||
#include "../shared/ToplevelManager.hpp"
|
||||
|
@ -43,6 +44,7 @@ class CPortalManager {
|
|||
|
||||
struct {
|
||||
std::unique_ptr<CScreencopyPortal> screencopy;
|
||||
std::unique_ptr<CScreenshotPortal> screenshot;
|
||||
std::unique_ptr<CGlobalShortcutsPortal> globalShortcuts;
|
||||
} m_sPortals;
|
||||
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
#include "MiscFunctions.hpp"
|
||||
#include <memory>
|
||||
#include <unistd.h>
|
||||
#include "../helpers/Log.hpp"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <filesystem>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
std::string execAndGet(const char* cmd) {
|
||||
Debug::log(LOG, "execAndGet: {}", cmd);
|
||||
|
||||
|
@ -25,3 +30,37 @@ void addHyprlandNotification(const std::string& icon, float timeMs, const std::s
|
|||
if (fork() == 0)
|
||||
execl("/bin/sh", "/bin/sh", "-c", CMD.c_str(), nullptr);
|
||||
}
|
||||
|
||||
bool inShellPath(const std::string& exec) {
|
||||
|
||||
if (exec.starts_with("/") || exec.starts_with("./") || exec.starts_with("../"))
|
||||
return std::filesystem::exists(exec);
|
||||
|
||||
// we are relative to our PATH
|
||||
const char* path = std::getenv("PATH");
|
||||
|
||||
if (!path)
|
||||
return false;
|
||||
|
||||
// collect paths
|
||||
std::string pathString = path;
|
||||
std::vector<std::string> paths;
|
||||
uint32_t nextBegin = 0;
|
||||
for (uint32_t i = 0; i < pathString.size(); i++) {
|
||||
if (path[i] == ':') {
|
||||
paths.push_back(pathString.substr(nextBegin, i - nextBegin));
|
||||
nextBegin = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextBegin < pathString.size())
|
||||
paths.push_back(pathString.substr(nextBegin, pathString.size() - nextBegin));
|
||||
|
||||
return std::ranges::any_of(paths, [&exec](std::string& path) { return std::filesystem::exists(path + "/" + exec); });
|
||||
}
|
||||
|
||||
void sendEmptyDbusMethodReply(sdbus::MethodCall& call, u_int32_t responseCode) {
|
||||
auto reply = call.createReply();
|
||||
reply << (uint32_t)responseCode;
|
||||
reply.send();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <sdbus-c++/Message.h>
|
||||
|
||||
std::string execAndGet(const char* cmd);
|
||||
void addHyprlandNotification(const std::string& icon, float timeMs, const std::string& color, const std::string& message);
|
||||
bool inShellPath(const std::string& exec);
|
||||
void sendEmptyDbusMethodReply(sdbus::MethodCall& call, u_int32_t responseCode);
|
189
src/portals/Screenshot.cpp
Normal file
189
src/portals/Screenshot.cpp
Normal file
|
@ -0,0 +1,189 @@
|
|||
#include "Screenshot.hpp"
|
||||
#include "../core/PortalManager.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "../helpers/MiscFunctions.hpp"
|
||||
|
||||
#include <regex>
|
||||
#include <filesystem>
|
||||
|
||||
void pickHyprPicker(sdbus::MethodCall& call) {
|
||||
const std::string HYPRPICKER_CMD = "hyprpicker --format=rgb --no-fancy";
|
||||
std::string rgbColor = execAndGet(HYPRPICKER_CMD.c_str());
|
||||
|
||||
if (rgbColor.size() > 12) {
|
||||
Debug::log(ERR, "hyprpicker returned strange output: " + rgbColor);
|
||||
sendEmptyDbusMethodReply(call, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 3> colors{0, 0, 0};
|
||||
|
||||
try {
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
uint64_t next = rgbColor.find(' ');
|
||||
|
||||
if (next == std::string::npos) {
|
||||
Debug::log(ERR, "hyprpicker returned strange output: " + rgbColor);
|
||||
sendEmptyDbusMethodReply(call, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
colors[i] = std::stoi(rgbColor.substr(0, next));
|
||||
rgbColor = rgbColor.substr(next + 1, rgbColor.size() - next);
|
||||
}
|
||||
colors[2] = std::stoi(rgbColor);
|
||||
} catch (...) {
|
||||
Debug::log(ERR, "Reading RGB values from hyprpicker failed. This is likely a string to integer error.");
|
||||
sendEmptyDbusMethodReply(call, 1);
|
||||
}
|
||||
|
||||
auto [r, g, b] = colors;
|
||||
std::unordered_map<std::string, sdbus::Variant> results;
|
||||
results["color"] = sdbus::Struct(std::tuple{r / 255.0, g / 255.0, b / 255.0});
|
||||
|
||||
auto reply = call.createReply();
|
||||
|
||||
reply << (uint32_t)0;
|
||||
reply << results;
|
||||
reply.send();
|
||||
}
|
||||
|
||||
void pickSlurp(sdbus::MethodCall& call) {
|
||||
const std::string PICK_COLOR_CMD = "grim -g \"$(slurp -p)\" -t ppm -";
|
||||
std::string ppmColor = execAndGet(PICK_COLOR_CMD.c_str());
|
||||
|
||||
// unify whitespace
|
||||
ppmColor = std::regex_replace(ppmColor, std::regex("\\s+"), std::string(" "));
|
||||
|
||||
// check if we got a 1x1 PPM Image
|
||||
if (!ppmColor.starts_with("P6 1 1 ")) {
|
||||
Debug::log(ERR, "grim did not return a PPM Image for us.");
|
||||
sendEmptyDbusMethodReply(call, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// convert it to a rgb value
|
||||
try {
|
||||
std::string maxValString = ppmColor.substr(7, ppmColor.size());
|
||||
maxValString = maxValString.substr(0, maxValString.find(' '));
|
||||
uint32_t maxVal = std::stoi(maxValString);
|
||||
|
||||
double r, g, b;
|
||||
|
||||
// 1 byte per triplet
|
||||
if (maxVal < 256) {
|
||||
std::string byteString = ppmColor.substr(11, 14);
|
||||
|
||||
r = (uint8_t)byteString[0] / (maxVal * 1.0);
|
||||
g = (uint8_t)byteString[1] / (maxVal * 1.0);
|
||||
b = (uint8_t)byteString[2] / (maxVal * 1.0);
|
||||
} else {
|
||||
// 2 byte per triplet (MSB first)
|
||||
std::string byteString = ppmColor.substr(11, 17);
|
||||
|
||||
r = ((byteString[0] << 8) | byteString[1]) / (maxVal * 1.0);
|
||||
g = ((byteString[2] << 8) | byteString[3]) / (maxVal * 1.0);
|
||||
b = ((byteString[4] << 8) | byteString[5]) / (maxVal * 1.0);
|
||||
}
|
||||
|
||||
auto reply = call.createReply();
|
||||
|
||||
std::unordered_map<std::string, sdbus::Variant> results;
|
||||
results["color"] = sdbus::Struct(std::tuple{r, g, b});
|
||||
|
||||
reply << (uint32_t)0;
|
||||
reply << results;
|
||||
reply.send();
|
||||
} catch (...) {
|
||||
Debug::log(ERR, "Converting PPM to RGB failed. This is likely a string to integer error.");
|
||||
sendEmptyDbusMethodReply(call, 1);
|
||||
}
|
||||
}
|
||||
|
||||
CScreenshotPortal::CScreenshotPortal() {
|
||||
m_pObject = sdbus::createObject(*g_pPortalManager->getConnection(), OBJECT_PATH);
|
||||
|
||||
m_pObject->registerMethod(INTERFACE_NAME, "Screenshot", "ossa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onScreenshot(c); });
|
||||
m_pObject->registerMethod(INTERFACE_NAME, "PickColor", "ossa{sv}", "ua{sv}", [&](sdbus::MethodCall c) { onPickColor(c); });
|
||||
|
||||
m_pObject->registerProperty(INTERFACE_NAME, "version", "u", [](sdbus::PropertyGetReply& reply) -> void { reply << (uint32_t)(2); });
|
||||
|
||||
m_pObject->finishRegistration();
|
||||
|
||||
Debug::log(LOG, "[screenshot] init successful");
|
||||
}
|
||||
|
||||
void CScreenshotPortal::onScreenshot(sdbus::MethodCall& call) {
|
||||
sdbus::ObjectPath requestHandle;
|
||||
call >> requestHandle;
|
||||
|
||||
std::string appID;
|
||||
call >> appID;
|
||||
|
||||
std::string parentWindow;
|
||||
call >> parentWindow;
|
||||
|
||||
std::unordered_map<std::string, sdbus::Variant> options;
|
||||
call >> options;
|
||||
|
||||
Debug::log(LOG, "[screenshot] New screenshot request:");
|
||||
Debug::log(LOG, "[screenshot] | {}", requestHandle.c_str());
|
||||
Debug::log(LOG, "[screenshot] | appid: {}", appID);
|
||||
|
||||
bool isInteractive = options.count("interactive") && options["interactive"].get<bool>() && inShellPath("slurp");
|
||||
|
||||
// make screenshot
|
||||
const std::string HYPR_DIR = "/tmp/hypr/";
|
||||
const std::string SNAP_FILE = "xdph_screenshot.png";
|
||||
const std::string FILE_PATH = HYPR_DIR + SNAP_FILE;
|
||||
const std::string SNAP_CMD = "grim " + FILE_PATH;
|
||||
const std::string SNAP_INTERACTIVE_CMD = "grim -g \"$(slurp)\" " + FILE_PATH;
|
||||
|
||||
std::unordered_map<std::string, sdbus::Variant> results;
|
||||
results["uri"] = "file://" + FILE_PATH;
|
||||
|
||||
std::filesystem::remove(FILE_PATH);
|
||||
std::filesystem::create_directory(HYPR_DIR);
|
||||
|
||||
if (isInteractive)
|
||||
execAndGet(SNAP_INTERACTIVE_CMD.c_str());
|
||||
else
|
||||
execAndGet(SNAP_CMD.c_str());
|
||||
|
||||
uint32_t responseCode = std::filesystem::exists(FILE_PATH) ? 0 : 1;
|
||||
|
||||
auto reply = call.createReply();
|
||||
reply << responseCode;
|
||||
reply << results;
|
||||
reply.send();
|
||||
}
|
||||
|
||||
void CScreenshotPortal::onPickColor(sdbus::MethodCall& call) {
|
||||
sdbus::ObjectPath requestHandle;
|
||||
call >> requestHandle;
|
||||
|
||||
std::string appID;
|
||||
call >> appID;
|
||||
|
||||
std::string parentWindow;
|
||||
call >> parentWindow;
|
||||
|
||||
Debug::log(LOG, "[screenshot] New PickColor request:");
|
||||
Debug::log(LOG, "[screenshot] | {}", requestHandle.c_str());
|
||||
Debug::log(LOG, "[screenshot] | appid: {}", appID);
|
||||
|
||||
bool hyprPickerInstalled = inShellPath("hyprpicker");
|
||||
bool slurpInstalled = inShellPath("slurp");
|
||||
|
||||
if (!slurpInstalled && !hyprPickerInstalled) {
|
||||
Debug::log(ERR, "Neither slurp nor hyprpicker found. We can't pick colors.");
|
||||
sendEmptyDbusMethodReply(call, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// use hyprpicker if installed, slurp as fallback
|
||||
if (hyprPickerInstalled)
|
||||
pickHyprPicker(call);
|
||||
else
|
||||
pickSlurp(call);
|
||||
}
|
18
src/portals/Screenshot.hpp
Normal file
18
src/portals/Screenshot.hpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <protocols/wlr-screencopy-unstable-v1-protocol.h>
|
||||
|
||||
class CScreenshotPortal {
|
||||
public:
|
||||
CScreenshotPortal();
|
||||
|
||||
void onScreenshot(sdbus::MethodCall& call);
|
||||
void onPickColor(sdbus::MethodCall& call);
|
||||
|
||||
private:
|
||||
std::unique_ptr<sdbus::IObject> m_pObject;
|
||||
|
||||
const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.Screenshot";
|
||||
const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop";
|
||||
};
|
Loading…
Reference in a new issue