diff --git a/hyprctl/main.cpp b/hyprctl/main.cpp index f4de085f..edae83e1 100644 --- a/hyprctl/main.cpp +++ b/hyprctl/main.cpp @@ -217,6 +217,21 @@ int setcursorRequest(int argc, char** argv) { return 0; } +int outputRequest(int argc, char** argv) { + if (argc < 4) { + std::cout << "Usage: hyprctl output \n\ + creates / destroys a fake output\n\ + with create, name is the backend name to use (available: auto, x11, wayland, headless)\n\ + with destroy, name is the output name to destroy"; + return 1; + } + + std::string rq = "output " + std::string(argv[2]) + " " + std::string(argv[3]); + + request(rq); + return 0; +} + void batchRequest(std::string arg) { std::string rq = "[[BATCH]]" + arg.substr(arg.find_first_of(" ") + 1); @@ -290,6 +305,7 @@ int main(int argc, char** argv) { else if (fullRequest.contains("/reload")) request(fullRequest); else if (fullRequest.contains("/getoption")) request(fullRequest); else if (fullRequest.contains("/cursorpos")) request(fullRequest); + else if (fullRequest.contains("/output")) exitStatus = outputRequest(argc, argv); else if (fullRequest.contains("/setcursor")) exitStatus = setcursorRequest(argc, argv); else if (fullRequest.contains("/dispatch")) exitStatus = dispatchRequest(argc, argv); else if (fullRequest.contains("/keyword")) exitStatus = keywordRequest(argc, argv); diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 1b2341fd..60779420 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -180,6 +180,15 @@ CCompositor::CCompositor() { m_sWLRIMEMgr = wlr_input_method_manager_v2_create(m_sWLDisplay); m_sWLRActivation = wlr_xdg_activation_v1_create(m_sWLDisplay); + + m_sWLRHeadlessBackend = wlr_headless_backend_create(m_sWLDisplay); + + if (!m_sWLRHeadlessBackend) { + Debug::log(CRIT, "Couldn't create the headless backend"); + throw std::runtime_error("wlr_headless_backend_create() failed!"); + } + + wlr_multi_backend_add(m_sWLRBackend, m_sWLRHeadlessBackend); } CCompositor::~CCompositor() { diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 7264a00a..c5cc5127 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -71,6 +71,7 @@ public: wlr_text_input_manager_v3* m_sWLRTextInputMgr; wlr_xdg_activation_v1* m_sWLRActivation; wlr_linux_dmabuf_v1* m_sWLRLinuxDMABuf; + wlr_backend* m_sWLRHeadlessBackend; // ------------------------------------------------- // diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index 494efcd4..e0438c09 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -743,6 +743,86 @@ R"#( } } +void createOutputIter(wlr_backend* backend, void* data) { + const auto DATA = (std::pair*)data; + + if (DATA->second) + return; + + if (DATA->first.empty() || DATA->first == "auto") { + if (wlr_backend_is_wl(backend)) { + wlr_wl_output_create(backend); + DATA->second = true; + } else if (wlr_backend_is_x11(backend)) { + wlr_x11_output_create(backend); + DATA->second = true; + } else if (wlr_backend_is_headless(backend)) { + wlr_headless_add_output(backend, 1920, 1080); + DATA->second = true; + } + } else { + if (wlr_backend_is_wl(backend) && DATA->first == "wayland") { + wlr_wl_output_create(backend); + DATA->second = true; + } else if (wlr_backend_is_x11(backend) && DATA->first == "x11") { + wlr_x11_output_create(backend); + DATA->second = true; + } else if (wlr_backend_is_headless(backend) && DATA->first == "headless") { + wlr_headless_add_output(backend, 1920, 1080); + DATA->second = true; + } + } +} + +std::string dispatchOutput(std::string request) { + std::string curitem = ""; + + auto nextItem = [&]() { + auto idx = request.find_first_of(' '); + + if (idx != std::string::npos) { + curitem = request.substr(0, idx); + request = request.substr(idx + 1); + } else { + curitem = request; + request = ""; + } + + curitem = removeBeginEndSpacesTabs(curitem); + }; + + nextItem(); + nextItem(); + + const auto MODE = curitem; + + nextItem(); + + const auto NAME = curitem; + + if (MODE == "create" || MODE == "add") { + std::pair result = { NAME, false }; + + wlr_multi_for_each_backend(g_pCompositor->m_sWLRBackend, createOutputIter, &result); + + if (!result.second) + return "no backend replied to the request"; + + } else if (MODE == "destroy" || MODE == "remove") { + const auto PMONITOR = g_pCompositor->getMonitorFromName(NAME); + + if (!PMONITOR) + return "output not found"; + + if (!PMONITOR->createdByUser) + return "cannot remove a real display. Use the monitor keyword."; + + wlr_output_destroy(PMONITOR->output); + } + + return "ok"; +} + std::string getReply(std::string request) { auto format = HyprCtl::FORMAT_NORMAL; @@ -786,6 +866,8 @@ std::string getReply(std::string request) { return splashRequest(); else if (request == "cursorpos") return cursorPosRequest(format); + else if (request.find("output") == 0) + return dispatchOutput(request); else if (request.find("dispatch") == 0) return dispatchRequest(request); else if (request.find("keyword") == 0) diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 17643b3f..6849c227 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -8,6 +8,9 @@ void CMonitor::onConnect(bool noRule) { szName = output->name; + if (!wlr_backend_is_drm(output->backend)) + createdByUser = true; // should be true. WL, X11 and Headless backends should be addable / removable + // get monitor rule that matches SMonitorRule monitorRule = g_pConfigManager->getMonitorRuleFor(output->name, output->description ? output->description : ""); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 476dd2ce..0f789781 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -40,6 +40,7 @@ public: bool dpmsStatus = true; bool vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it. bool enabled10bit = false; // as above, this can be TRUE even if 10 bit failed. + bool createdByUser = false; // mirroring CMonitor* pMirrorOf = nullptr; diff --git a/src/helpers/XWaylandStubs.hpp b/src/helpers/XWaylandStubs.hpp index 395a7575..97147f76 100644 --- a/src/helpers/XWaylandStubs.hpp +++ b/src/helpers/XWaylandStubs.hpp @@ -154,4 +154,8 @@ inline void wlr_xwayland_surface_close(wlr_xwayland_surface*) { } inline void wlr_xwayland_surface_set_fullscreen(wlr_xwayland_surface*, bool) { } -inline void wlr_xwayland_surface_set_minimized(wlr_xwayland_surface *, bool) {} +inline void wlr_xwayland_surface_set_minimized(wlr_xwayland_surface *, bool) { } + +inline bool wlr_backend_is_x11(void*) { return false; } + +inline void wlr_x11_output_create(void*) { } \ No newline at end of file diff --git a/src/includes.hpp b/src/includes.hpp index a983ebb8..55c795f5 100644 --- a/src/includes.hpp +++ b/src/includes.hpp @@ -99,10 +99,15 @@ extern "C" { #include #include #include +#include +#include +#include +#include #include #ifndef NO_XWAYLAND +#include #include #include #endif diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 3858b24e..2e7c5433 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1347,6 +1347,13 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR pMonitor->vecSize = (Vector2D(x, y) / pMonitor->scale).floor(); pMonitor->vecTransformedSize = Vector2D(x,y); + if (pMonitor->createdByUser) { + wlr_box transformedBox = { 0, 0, (int)pMonitor->vecTransformedSize.x, (int)pMonitor->vecTransformedSize.y }; + wlr_box_transform(&transformedBox, &transformedBox, wlr_output_transform_invert(pMonitor->output->transform), (int)pMonitor->vecTransformedSize.x, (int)pMonitor->vecTransformedSize.y); + + pMonitor->vecPixelSize = Vector2D(transformedBox.width, transformedBox.height); + } + if (pMonitorRule->offset == Vector2D(-1, -1) && pMonitor->vecPosition == Vector2D(-1, -1)) { // let's find manually a sensible position for it, to the right. Vector2D finalPos;