mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-11-21 22:25: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]
|
[portal]
|
||||||
DBusName=org.freedesktop.impl.portal.desktop.hyprland
|
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;
|
UseIn=wlroots;Hyprland;sway;Wayfire;river;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "PortalManager.hpp"
|
#include "PortalManager.hpp"
|
||||||
#include "../helpers/Log.hpp"
|
#include "../helpers/Log.hpp"
|
||||||
|
#include "../helpers/MiscFunctions.hpp"
|
||||||
|
|
||||||
#include <protocols/hyprland-global-shortcuts-v1-protocol.h>
|
#include <protocols/hyprland-global-shortcuts-v1-protocol.h>
|
||||||
#include <protocols/hyprland-toplevel-export-v1-protocol.h>
|
#include <protocols/hyprland-toplevel-export-v1-protocol.h>
|
||||||
|
@ -292,6 +293,20 @@ void CPortalManager::init() {
|
||||||
else if (m_sWaylandConnection.hyprlandToplevelMgr)
|
else if (m_sWaylandConnection.hyprlandToplevelMgr)
|
||||||
m_sPortals.screencopy->appendToplevelExport(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);
|
wl_display_roundtrip(m_sWaylandConnection.display);
|
||||||
|
|
||||||
startEventLoop();
|
startEventLoop();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <wayland-client.h>
|
#include <wayland-client.h>
|
||||||
|
|
||||||
#include "../portals/Screencopy.hpp"
|
#include "../portals/Screencopy.hpp"
|
||||||
|
#include "../portals/Screenshot.hpp"
|
||||||
#include "../portals/GlobalShortcuts.hpp"
|
#include "../portals/GlobalShortcuts.hpp"
|
||||||
#include "../helpers/Timer.hpp"
|
#include "../helpers/Timer.hpp"
|
||||||
#include "../shared/ToplevelManager.hpp"
|
#include "../shared/ToplevelManager.hpp"
|
||||||
|
@ -43,6 +44,7 @@ class CPortalManager {
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
std::unique_ptr<CScreencopyPortal> screencopy;
|
std::unique_ptr<CScreencopyPortal> screencopy;
|
||||||
|
std::unique_ptr<CScreenshotPortal> screenshot;
|
||||||
std::unique_ptr<CGlobalShortcutsPortal> globalShortcuts;
|
std::unique_ptr<CGlobalShortcutsPortal> globalShortcuts;
|
||||||
} m_sPortals;
|
} m_sPortals;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
#include "MiscFunctions.hpp"
|
#include "MiscFunctions.hpp"
|
||||||
#include <memory>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include "../helpers/Log.hpp"
|
#include "../helpers/Log.hpp"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
std::string execAndGet(const char* cmd) {
|
std::string execAndGet(const char* cmd) {
|
||||||
Debug::log(LOG, "execAndGet: {}", cmd);
|
Debug::log(LOG, "execAndGet: {}", cmd);
|
||||||
|
|
||||||
|
@ -25,3 +30,37 @@ void addHyprlandNotification(const std::string& icon, float timeMs, const std::s
|
||||||
if (fork() == 0)
|
if (fork() == 0)
|
||||||
execl("/bin/sh", "/bin/sh", "-c", CMD.c_str(), nullptr);
|
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
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <sdbus-c++/Message.h>
|
||||||
|
|
||||||
std::string execAndGet(const char* cmd);
|
std::string execAndGet(const char* cmd);
|
||||||
void addHyprlandNotification(const std::string& icon, float timeMs, const std::string& color, const std::string& message);
|
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