├── src ├── utilities │ ├── Tray.hpp │ ├── Workspace.cpp │ ├── AnimationUtil.hpp │ ├── Debug.hpp │ ├── Keybind.cpp │ ├── XCBProps.hpp │ ├── Keybind.hpp │ ├── Monitor.hpp │ ├── Workspace.hpp │ ├── Debug.cpp │ ├── Util.hpp │ ├── Util.cpp │ ├── XCBProps.cpp │ └── AnimationUtil.cpp ├── bar │ ├── BarCommands.hpp │ ├── Bar.hpp │ ├── BarCommands.cpp │ └── Bar.cpp ├── helpers │ ├── Vector.cpp │ └── Vector.hpp ├── ewmh │ ├── ewmh.hpp │ └── ewmh.cpp ├── ipc │ ├── ipc.hpp │ └── ipc.cpp ├── events │ ├── events.hpp │ └── events.cpp ├── config │ ├── ConfigManager.hpp │ ├── defaultConfig.hpp │ └── ConfigManager.cpp ├── KeybindManager.hpp ├── main.cpp ├── defines.hpp ├── window.hpp ├── window.cpp ├── windowManager.hpp └── KeybindManager.cpp ├── .gitignore ├── example ├── hypr.desktop ├── .vscode │ ├── tasks.json │ ├── launch.json │ └── settings.json └── hypr.conf ├── Makefile ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── report-a-bug.md └── workflows │ ├── c-cpp.yml │ └── codeql-analysis.yml ├── CONTRIBUTING.md ├── LICENSE ├── CMakeLists.txt └── README.md /src/utilities/Tray.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class CTrayClient { 4 | public: 5 | uint32_t window = 0; 6 | int XEVer = -1; 7 | bool hidden = false; 8 | }; -------------------------------------------------------------------------------- /src/utilities/Workspace.cpp: -------------------------------------------------------------------------------- 1 | #include "Workspace.hpp" 2 | 3 | CWorkspace::CWorkspace() { this->m_bHasFullscreenWindow = false; this->m_bAnimationInProgress = false; } 4 | CWorkspace::~CWorkspace() { } -------------------------------------------------------------------------------- /src/utilities/AnimationUtil.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Util.hpp" 7 | 8 | namespace AnimationUtil { 9 | 10 | void move(); 11 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | cmake_install.cmake 7 | install_manifest.txt 8 | compile_commands.json 9 | CTestTestfile.cmake 10 | _deps 11 | 12 | build/ 13 | /.vscode/ -------------------------------------------------------------------------------- /example/hypr.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Hypr 3 | Comment=A tiling window manager written in modern C++ 4 | Exec=Hypr 5 | TryExec=Hypr 6 | Type=Application 7 | X-LightDM-DesktopName=Hypr 8 | DesktopNames=Hypr 9 | Keywords=tiling;wm;window;manager;Hypr; -------------------------------------------------------------------------------- /src/utilities/Debug.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | enum LogLevel { 5 | NONE = -1, 6 | LOG = 0, 7 | WARN, 8 | ERR, 9 | CRIT 10 | }; 11 | 12 | namespace Debug { 13 | void log(LogLevel, std::string msg); 14 | }; -------------------------------------------------------------------------------- /example/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "setdisplay", 6 | "command": "export DISPLAY=:1", // Could be any other shell command 7 | "type": "shell" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /src/bar/BarCommands.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../defines.hpp" 3 | #include "../utilities/Util.hpp" 4 | 5 | namespace BarCommands { 6 | std::string parseCommand(std::string); 7 | 8 | std::string parsePercent(std::string); 9 | std::string parseDollar(std::string); 10 | }; -------------------------------------------------------------------------------- /src/utilities/Keybind.cpp: -------------------------------------------------------------------------------- 1 | #include "Keybind.hpp" 2 | 3 | Keybind::Keybind(unsigned int mod, xcb_keysym_t keysym, std::string comm, Dispatcher disp) { 4 | this->m_iMod = mod; 5 | this->m_Keysym = keysym; 6 | this->m_szCommand = comm; 7 | this->m_pDispatcher = disp; 8 | } 9 | 10 | Keybind::~Keybind() { } -------------------------------------------------------------------------------- /src/utilities/XCBProps.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | std::pair getClassName(int64_t window); 8 | std::string getRoleName(int64_t window); 9 | std::string getWindowName(uint64_t window); 10 | uint8_t getWindowState(const int& win); 11 | 12 | void removeAtom(const int& window, xcb_atom_t prop, xcb_atom_t atom); -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: cmake 2 | 3 | release: 4 | mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja 5 | cmake --build ./build --config Release --target all -j 10 6 | 7 | debug: 8 | mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -H./ -B./build -G Ninja 9 | cmake --build ./build --config Debug --target all -j 10 10 | 11 | clear: 12 | rm -rf build 13 | -------------------------------------------------------------------------------- /src/helpers/Vector.cpp: -------------------------------------------------------------------------------- 1 | #include "Vector.hpp" 2 | #include 3 | 4 | Vector2D::Vector2D(double xx, double yy) { 5 | x = xx; 6 | y = yy; 7 | } 8 | 9 | Vector2D::Vector2D() { x = 0; y = 0; } 10 | Vector2D::~Vector2D() {} 11 | 12 | double Vector2D::normalize() { 13 | // get max abs 14 | const auto max = abs(x) > abs(y) ? abs(x) : abs(y); 15 | 16 | x /= max; 17 | y /= max; 18 | 19 | return max; 20 | } 21 | -------------------------------------------------------------------------------- /src/utilities/Keybind.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../defines.hpp" 3 | 4 | typedef void (*Dispatcher)(std::string); 5 | 6 | class Keybind { 7 | public: 8 | Keybind(unsigned int, xcb_keysym_t, std::string, Dispatcher); 9 | ~Keybind(); 10 | 11 | EXPOSED_MEMBER(Mod, unsigned int, i); 12 | EXPOSED_MEMBER(Keysym, xcb_keysym_t,); 13 | EXPOSED_MEMBER(Command, std::string, sz); 14 | EXPOSED_MEMBER(Dispatcher, Dispatcher, p); 15 | }; 16 | 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature idea for Hypr 4 | title: '' 5 | labels: enhancement 6 | assignees: vaxerski 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Anything else?** 17 | -------------------------------------------------------------------------------- /src/utilities/Monitor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../defines.hpp" 4 | 5 | struct SMonitor { 6 | Vector2D vecPosition = Vector2D(0,0); 7 | Vector2D vecSize = Vector2D(0,0); 8 | 9 | bool hasABar = false; 10 | bool primary = false; 11 | 12 | int ID = -1; 13 | 14 | std::string szName = ""; 15 | 16 | Vector2D vecReservedTopLeft = Vector2D(0,0); 17 | Vector2D vecReservedBottomRight = Vector2D(0,0); 18 | }; -------------------------------------------------------------------------------- /src/utilities/Workspace.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../defines.hpp" 4 | 5 | class CWorkspace { 6 | public: 7 | CWorkspace(); 8 | ~CWorkspace(); 9 | 10 | EXPOSED_MEMBER(ID, int, i); 11 | EXPOSED_MEMBER(LastWindow, xcb_drawable_t, i); 12 | 13 | EXPOSED_MEMBER(Monitor, int, i); 14 | 15 | EXPOSED_MEMBER(HasFullscreenWindow, bool, b); 16 | 17 | // Wipe animations 18 | EXPOSED_MEMBER(AnimationInProgress, bool, b); 19 | EXPOSED_MEMBER(CurrentOffset, Vector2D, vec); 20 | EXPOSED_MEMBER(GoalOffset, Vector2D, vec); 21 | }; 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributing is very welcome, but please read this before starting. 4 | 5 | ## How to? 6 | I track everything on GitHub. I know torvalds said it's ass, but I can't be bothered to use anything else. 7 | 8 | First off, make sure there is no PR for what you are planning to make. 9 | 10 | Second, make double and triple sure that what you want to make is *actually useful*. Please do not open a PR for a typo. Open an issue for such things instead. 11 | 12 | That's it for now I guess. Thanks for considering contributing to Hypr! 13 | 14 | ## =) 15 | -------------------------------------------------------------------------------- /src/ewmh/ewmh.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../defines.hpp" 4 | 5 | namespace EWMH { 6 | void setupInitEWMH(); 7 | void updateCurrentWindow(xcb_window_t); 8 | void updateWindow(xcb_window_t); 9 | void updateClientList(); 10 | void setFrameExtents(xcb_window_t); 11 | void refreshAllExtents(); 12 | void updateDesktops(); 13 | void checkTransient(xcb_window_t); 14 | 15 | namespace DesktopInfo { 16 | inline int lastid = 0; 17 | }; 18 | 19 | inline xcb_window_t EWMHwindow = XCB_WINDOW_NONE; 20 | }; 21 | -------------------------------------------------------------------------------- /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: getpkgs 17 | run: sudo apt-get update && sudo apt install xcb cmake gcc libgtk-3-dev ninja-build libgtkmm-3.0-dev libxcb-randr0 libxcb-randr0-dev libxcb-util-dev libxcb-util0-dev libxcb-util1 libxcb-ewmh-dev libxcb-xinerama0 libxcb-xinerama0-dev libxcb-icccm4-dev libxcb-keysyms1-dev libxcb-cursor-dev libxcb-shape0-dev 18 | - name: configure 19 | run: make clear 20 | - name: make 21 | run: make release 22 | -------------------------------------------------------------------------------- /src/helpers/Vector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Vector2D { 6 | public: 7 | Vector2D(double, double); 8 | Vector2D(); 9 | ~Vector2D(); 10 | 11 | double x = 0; 12 | double y = 0; 13 | 14 | // returns the scale 15 | double normalize(); 16 | 17 | Vector2D operator+(Vector2D a) { 18 | return Vector2D(this->x + a.x, this->y + a.y); 19 | } 20 | Vector2D operator-(Vector2D a) { 21 | return Vector2D(this->x - a.x, this->y - a.y); 22 | } 23 | Vector2D operator*(float a) { 24 | return Vector2D(this->x * a, this->y * a); 25 | } 26 | Vector2D operator/(float a) { 27 | return Vector2D(this->x / a, this->y / a); 28 | } 29 | }; -------------------------------------------------------------------------------- /src/utilities/Debug.cpp: -------------------------------------------------------------------------------- 1 | #include "Debug.hpp" 2 | #include 3 | #include "../windowManager.hpp" 4 | 5 | void Debug::log(LogLevel level, std::string msg) { 6 | switch (level) 7 | { 8 | case LOG: 9 | msg = "[LOG] " + msg; 10 | break; 11 | 12 | case WARN: 13 | msg = "[WARN] " + msg; 14 | break; 15 | 16 | case ERR: 17 | msg = "[ERR] " + msg; 18 | break; 19 | 20 | case CRIT: 21 | msg = "[CRITICAL] " + msg; 22 | break; 23 | 24 | default: 25 | break; 26 | } 27 | printf("%s", (msg + "\n").c_str()); 28 | 29 | // also log to a file 30 | const std::string DEBUGPATH = ISDEBUG ? "/tmp/hypr/hyprd.log" : "/tmp/hypr/hypr.log"; 31 | std::ofstream ofs; 32 | ofs.open(DEBUGPATH, std::ios::out | std::ios::app); 33 | ofs << msg << "\n"; 34 | ofs.close(); 35 | } 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report-a-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a Bug 3 | about: Report a bug in order to get it fixed. 4 | title: '' 5 | labels: bug 6 | assignees: vaxerski 7 | 8 | --- 9 | 10 | **Please describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Steps to reproduce:** 14 | Do a, use program b... 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Anything else?** 23 | 24 | **Log:** 25 | Please attach a log. (paste it into pastebin and paste here the url) The log can be found in a temp file located in /tmp/hypr/hypr.log. 26 | 27 | **Coredump:** 28 | If Hypr crashed, please attach a coredump. (paste into pastebind and paste here the url) 29 | 30 | How to? 31 | 32 | Systemd instructions: 33 | `coredumpctl` 34 | find the last ocurrence of Hypr and note the PID. 35 | `coredumpctl info ` 36 | will print the coredump. 37 | `coredumpctl info --no-pager | xclip -sel clip` 38 | will copy it to the clipboard. 39 | -------------------------------------------------------------------------------- /example/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(gdb) Launch", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "environment": [ 12 | { 13 | "name": "DISPLAY", 14 | "value": ":1" // Set to whatever value you want. 15 | } 16 | ], 17 | "program": "${workspaceFolder}/build/Hypr", 18 | "args": [], 19 | "stopAtEntry": false, 20 | "cwd": "${fileDirname}", 21 | "externalConsole": false, 22 | "MIMode": "gdb", 23 | "setupCommands": [ 24 | { 25 | "description": "Enable pretty-printing for gdb", 26 | "text": "-enable-pretty-printing", 27 | "ignoreFailures": true 28 | } 29 | ] 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /src/ipc/ipc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../defines.hpp" 3 | 4 | std::string readFromIPCChannel(const std::string); 5 | int writeToIPCChannel(const std::string, std::string); 6 | 7 | #define IPC_END_OF_FILE (std::string)"HYPR_END_OF_FILE" 8 | #define IPC_MESSAGE_SEPARATOR std::string("\t") 9 | #define IPC_MESSAGE_EQUALITY std::string("=") 10 | 11 | struct SIPCMessageMainToBar { 12 | std::vector openWorkspaces; 13 | uint64_t activeWorkspace; 14 | std::string lastWindowName; 15 | std::string lastWindowClass; 16 | bool fullscreenOnBar; 17 | }; 18 | 19 | struct SIPCMessageBarToMain { 20 | uint64_t windowID; 21 | }; 22 | 23 | struct SIPCPipe { 24 | std::string szPipeName = ""; 25 | uint64_t iPipeFD = 0; 26 | }; 27 | 28 | // /tmp/ is RAM so the speeds will be decent, if anyone wants to implement 29 | // actual pipes feel free. 30 | 31 | void IPCSendMessage(const std::string, SIPCMessageMainToBar); 32 | void IPCSendMessage(const std::string, SIPCMessageBarToMain); 33 | void IPCRecieveMessageB(const std::string); 34 | void IPCRecieveMessageM(const std::string); -------------------------------------------------------------------------------- /src/events/events.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "../windowManager.hpp" 6 | 7 | namespace Events { 8 | EVENT(Enter); 9 | EVENT(Leave); 10 | EVENT(Destroy); 11 | EVENT(MapWindow); 12 | EVENT(UnmapWindow); 13 | EVENT(ButtonPress); 14 | EVENT(ButtonRelease); 15 | EVENT(Expose); 16 | EVENT(KeyPress); 17 | EVENT(MotionNotify); 18 | EVENT(ClientMessage); 19 | EVENT(Configure); 20 | 21 | EVENT(RandRScreenChange); 22 | 23 | // Bypass some events for floating windows 24 | CWindow* remapWindow(int, bool floating = false, int forcemonitor = -1); 25 | CWindow* remapFloatingWindow(int, int forcemonitor = -1); 26 | 27 | // A thread to notify xcb to redraw our shiz 28 | void redraw(); 29 | void setThread(); 30 | 31 | // For docks etc 32 | inline bool nextWindowCentered = false; 33 | 34 | // Fix focus on open 35 | inline std::deque ignoredEvents; 36 | 37 | // Fix spammed RandR events 38 | inline std::chrono::high_resolution_clock::time_point lastRandREvent = std::chrono::high_resolution_clock::now(); 39 | inline int susRandREventNo = 0; 40 | }; -------------------------------------------------------------------------------- /src/config/ConfigManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../utilities/Debug.hpp" 5 | #include 6 | #include "../defines.hpp" 7 | #include 8 | 9 | #include "defaultConfig.hpp" 10 | 11 | enum ELayouts { 12 | LAYOUT_DWINDLE = 0, 13 | LAYOUT_MASTER 14 | }; 15 | 16 | struct SConfigValue { 17 | int64_t intValue = -1; 18 | float floatValue = -1; 19 | std::string strValue = ""; 20 | }; 21 | 22 | struct SWindowRule { 23 | std::string szRule; 24 | std::string szValue; 25 | }; 26 | 27 | namespace ConfigManager { 28 | inline std::unordered_map configValues; 29 | inline time_t lastModifyTime = 0; 30 | 31 | inline bool loadBar = false; 32 | 33 | inline std::string currentCategory = ""; 34 | 35 | inline bool isFirstLaunch = false; 36 | 37 | inline std::string parseError = ""; // For storing a parse error to display later 38 | 39 | inline std::vector windowRules; 40 | 41 | void init(); 42 | void loadConfigLoadVars(); 43 | void tick(); 44 | 45 | void applyKeybindsToX(); 46 | 47 | std::vector getMatchingRules(xcb_window_t); 48 | 49 | int getInt(std::string); 50 | float getFloat(std::string); 51 | std::string getString(std::string); 52 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, vaxerski 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/KeybindManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "utilities/Keybind.hpp" 4 | #include 5 | #include "windowManager.hpp" 6 | 7 | #include 8 | 9 | namespace KeybindManager { 10 | inline std::vector keybinds; 11 | 12 | unsigned int modToMask(std::string); 13 | 14 | Keybind* findKeybindByKey(int mod, xcb_keysym_t keysym); 15 | xcb_keysym_t getKeysymFromKeycode(xcb_keycode_t keycode); 16 | xcb_keycode_t getKeycodeFromKeysym(xcb_keysym_t keysym); 17 | 18 | uint32_t getKeyCodeFromName(std::string); 19 | 20 | // Dispatchers 21 | void call(std::string args); 22 | void killactive(std::string args); 23 | void movewindow(std::string args); 24 | void movefocus(std::string args); 25 | void changeworkspace(std::string args); 26 | void changetolastworkspace(std::string args); 27 | void toggleActiveWindowFullscreen(std::string args); 28 | void toggleActiveWindowFloating(std::string args); 29 | void movetoworkspace(std::string args); 30 | void movetorelativeworkspace(std::string args); 31 | void changeSplitRatio(std::string args); 32 | void togglePseudoActive(std::string args); 33 | void toggleScratchpad(std::string args); 34 | void nextWorkspace(std::string args); 35 | void lastWorkspace(std::string args); 36 | void pinActive(std::string args); 37 | }; 38 | -------------------------------------------------------------------------------- /src/utilities/Util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../defines.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | // For precise colors 9 | class CFloatingColor { 10 | public: 11 | float r = 0; 12 | float g = 0; 13 | float b = 0; 14 | float a = 255; 15 | 16 | uint32_t getAsUint32() { 17 | return ((int)round(a)) * 0x1000000 + ((int)round(r)) * 0x10000 + ((int)round(g)) * 0x100 + ((int)round(b)); 18 | } 19 | 20 | CFloatingColor(uint32_t c) { 21 | r = RED(c) * 255.f; 22 | g = GREEN(c) * 255.f; 23 | b = BLUE(c) * 255.f; 24 | a = ALPHA(c) * 255.f; 25 | } 26 | 27 | CFloatingColor() { 28 | ; 29 | } 30 | 31 | CFloatingColor& operator=(uint32_t c) { 32 | r = RED(c) * 255.f; 33 | g = GREEN(c) * 255.f; 34 | b = BLUE(c) * 255.f; 35 | a = ALPHA(c) * 255.f; 36 | return *this; 37 | } 38 | 39 | bool operator==(CFloatingColor B) { 40 | return r == B.r && g == B.g && b == B.b && a == B.a; 41 | } 42 | 43 | bool operator!=(CFloatingColor B) { 44 | return !(r == B.r && g == B.g && b == B.b && a == B.a); 45 | } 46 | }; 47 | 48 | enum EDockAlign { 49 | DOCK_LEFT = 0, 50 | DOCK_RIGHT, 51 | DOCK_TOP, 52 | DOCK_BOTTOM 53 | }; 54 | 55 | std::string exec(const char* cmd); 56 | void clearLogs(); 57 | void emptyEvent(xcb_drawable_t window = 0); 58 | void wakeUpEvent(xcb_drawable_t window); 59 | bool xcbContainsAtom(xcb_get_property_reply_t* PROP, xcb_atom_t ATOM); 60 | 61 | CFloatingColor parabolicColor(CFloatingColor from, uint32_t to, double incline); 62 | CFloatingColor parabolicColor(CFloatingColor from, CFloatingColor to, double incline); 63 | 64 | double parabolic(double from, double to, double incline); 65 | 66 | std::vector splitString(std::string, char); -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | set(CMAKE_CXX_COMPILER "/bin/g++") 4 | 5 | project(Hypr 6 | VERSION 0.1 7 | DESCRIPTION "A Modern OOP C++ Window Manager" 8 | ) 9 | 10 | set(CMAKE_MESSAGE_LOG_LEVEL "STATUS") 11 | 12 | message(STATUS "Configuring Hypr!") 13 | 14 | add_compile_options(-std=c++17) 15 | add_compile_options(-Wall -Wextra) 16 | find_package(Threads REQUIRED) 17 | 18 | find_package(PkgConfig REQUIRED) 19 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET glib-2.0 harfbuzz cairo gtkmm-3.0 xcb-randr xcb-ewmh xcb-xinerama xcb-cursor xcb-keysyms xcb-icccm) 20 | 21 | file(GLOB_RECURSE SRCFILES "src/*.cpp") 22 | 23 | add_executable(Hypr ${SRCFILES}) 24 | 25 | IF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) 26 | message(STATUS "Configuring Hypr in Debug with CMake!") 27 | ELSE() 28 | add_compile_options(-Ofast) 29 | message(STATUS "Configuring Hypr in Release with CMake!") 30 | ENDIF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) 31 | 32 | IF(UNIX AND NOT APPLE) 33 | target_link_libraries(Hypr rt) 34 | ENDIF() 35 | 36 | set(CPACK_PROJECT_NAME ${PROJECT_NAME}) 37 | set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) 38 | include(CPack) 39 | 40 | target_link_libraries(Hypr PkgConfig::deps) 41 | 42 | target_link_libraries(Hypr 43 | xcb 44 | xcb-ewmh 45 | xcb-icccm 46 | xcb-keysyms 47 | xcb-randr 48 | xcb-xinerama 49 | xcb-cursor 50 | xcb-shape 51 | xcb-util 52 | ${CMAKE_THREAD_LIBS_INIT} 53 | ) 54 | 55 | IF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) 56 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg -no-pie -fno-builtin") 57 | SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg -no-pie -fno-builtin") 58 | SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg -no-pie -fno-builtin") 59 | ENDIF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) -------------------------------------------------------------------------------- /example/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", 3 | "cmake.configureOnOpen": true, 4 | "files.associations": { 5 | "cctype": "cpp", 6 | "clocale": "cpp", 7 | "cmath": "cpp", 8 | "cstdarg": "cpp", 9 | "cstddef": "cpp", 10 | "cstdio": "cpp", 11 | "cstdlib": "cpp", 12 | "cstring": "cpp", 13 | "ctime": "cpp", 14 | "cwchar": "cpp", 15 | "cwctype": "cpp", 16 | "array": "cpp", 17 | "atomic": "cpp", 18 | "hash_map": "cpp", 19 | "bit": "cpp", 20 | "*.tcc": "cpp", 21 | "bitset": "cpp", 22 | "chrono": "cpp", 23 | "compare": "cpp", 24 | "complex": "cpp", 25 | "concepts": "cpp", 26 | "condition_variable": "cpp", 27 | "cstdint": "cpp", 28 | "deque": "cpp", 29 | "list": "cpp", 30 | "map": "cpp", 31 | "set": "cpp", 32 | "string": "cpp", 33 | "unordered_map": "cpp", 34 | "unordered_set": "cpp", 35 | "vector": "cpp", 36 | "exception": "cpp", 37 | "algorithm": "cpp", 38 | "functional": "cpp", 39 | "iterator": "cpp", 40 | "memory": "cpp", 41 | "memory_resource": "cpp", 42 | "netfwd": "cpp", 43 | "numeric": "cpp", 44 | "random": "cpp", 45 | "ratio": "cpp", 46 | "string_view": "cpp", 47 | "system_error": "cpp", 48 | "tuple": "cpp", 49 | "type_traits": "cpp", 50 | "utility": "cpp", 51 | "fstream": "cpp", 52 | "initializer_list": "cpp", 53 | "iomanip": "cpp", 54 | "iosfwd": "cpp", 55 | "iostream": "cpp", 56 | "istream": "cpp", 57 | "limits": "cpp", 58 | "mutex": "cpp", 59 | "new": "cpp", 60 | "numbers": "cpp", 61 | "ostream": "cpp", 62 | "semaphore": "cpp", 63 | "sstream": "cpp", 64 | "stdexcept": "cpp", 65 | "stop_token": "cpp", 66 | "streambuf": "cpp", 67 | "thread": "cpp", 68 | "cinttypes": "cpp", 69 | "typeinfo": "cpp", 70 | "valarray": "cpp", 71 | "variant": "cpp", 72 | "codecvt": "cpp" 73 | } 74 | } -------------------------------------------------------------------------------- /src/config/defaultConfig.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline const std::string AUTOCONFIG = R"#( 6 | ######################################################################################## 7 | AUTOGENERATED HYPR CONFIG. 8 | PLEASE USE THE CONFIG PROVIDED IN THE GIT REPO /examples/hypr.conf AND EDIT IT, 9 | OR EDIT THIS ONE ACCORDING TO THE WIKI INSTRUCTIONS. 10 | ######################################################################################## 11 | 12 | autogenerated=1 # remove this line to get rid of the warning on top. 13 | 14 | gaps_in=5 15 | border_size=1 16 | gaps_out=20 17 | rounding=0 18 | max_fps=60 # max fps for updates of config & animations 19 | focus_when_hover=1 # 0 - do not switch the focus when hover (only for tiling) 20 | main_mod=SUPER # For moving, resizing 21 | intelligent_transients=1 # keeps transients always on top. 22 | no_unmap_saving=1 # disables saving unmapped windows (seems to break sometimes) 23 | scratchpad_mon=0 # self-explanatory 24 | 25 | # Layout 26 | layout=0 # 0 - dwindle (default), 1 - master 27 | layout { 28 | no_gaps_when_only=0 # disables gaps and borders when only window on screen 29 | } 30 | 31 | # Bar config 32 | Bar { 33 | height=20 34 | monitor=0 35 | enabled=1 36 | mod_pad_in=8 37 | no_tray_saving=1 # using this doesnt save the tray between reloads but fixes an issue with the bar disappearing. 38 | 39 | font.main=Noto Sans 40 | font.secondary=Noto Sans 41 | 42 | col.bg=0xff111111 43 | col.high=0xffff3333 44 | 45 | module=left,X,0xff8000ff,0xffffffff,1,workspaces 46 | module=pad,left,10 47 | module=left,,0xff7000dd,0xff7000dd,1,tray 48 | module=right,X,0xffffffff,0xff00ff33,1000,$date +%a,\ %b\ %Y\ \ %I:%M\ %p$ 49 | } 50 | 51 | # colors 52 | col.active_border=0x77ff3333 53 | col.inactive_border=0x77222222 54 | 55 | # animations 56 | Animations { 57 | enabled=1 58 | speed=5 # for workspaces 59 | window_resize_speed=5 # for windows 60 | cheap=1 # highly recommended 61 | borders=0 62 | workspaces=0 # not really recommended 63 | } 64 | 65 | # keybinds 66 | bind=SUPER,R,exec,dmenu_run 67 | bind=SUPER,T,exec,alacritty 68 | bind=SUPER,M,exec,pkill Hypr 69 | bind=SUPER,RETURN,exec,xterm 70 | bind=SUPER,G,exec,google-chrome-stable 71 | 72 | bind=SUPER,C,killactive, 73 | 74 | bind=SUPER,F,fullscreen, 75 | 76 | bind=SUPER,1,workspace,1 77 | bind=SUPER,2,workspace,2 78 | bind=SUPER,3,workspace,3 79 | bind=SUPER,4,workspace,4 80 | bind=SUPER,5,workspace,5 81 | bind=SUPER,6,workspace,6 82 | bind=SUPER,7,workspace,7 83 | bind=SUPER,8,workspace,8 84 | bind=SUPER,9,workspace,9 85 | )#"; -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Hypr Window Manager for X. 4 | Started by Vaxry on 2021 / 11 / 17 5 | 6 | */ 7 | 8 | #include 9 | #include "windowManager.hpp" 10 | #include "defines.hpp" 11 | #include "bar/Bar.hpp" 12 | 13 | int main(int argc, char** argv) { 14 | clearLogs(); 15 | 16 | Debug::log(LOG, "Hypr debug log. Built on " + std::string(__DATE__) + " at " + std::string(__TIME__)); 17 | 18 | // Create all pipes 19 | g_pWindowManager->createAndOpenAllPipes(); 20 | 21 | Debug::log(LOG, "Pipes done! Forking!"); 22 | 23 | if (fork() == 0) { 24 | // Child. Bar. 25 | 26 | // Sleep for 2 seconds. When launching on a real Xorg session there is some race condition there 27 | // I don't know where it is but this will fix it for now. 28 | // Feel free to search for it. 29 | std::this_thread::sleep_for(std::chrono::seconds(2)); 30 | 31 | const int BARRET = barMainThread(); 32 | Debug::log(BARRET == 0 ? LOG : ERR, "Bar exited with code " + std::to_string(BARRET) + "!"); 33 | return 0; 34 | } 35 | 36 | Debug::log(LOG, "Parent continuing!"); 37 | 38 | g_pWindowManager->DisplayConnection = xcb_connect(NULL, NULL); 39 | if (const auto RET = xcb_connection_has_error(g_pWindowManager->DisplayConnection); RET != 0) { 40 | Debug::log(CRIT, "Connection Failed! Return: " + std::to_string(RET)); 41 | return RET; 42 | } 43 | 44 | g_pWindowManager->Screen = xcb_setup_roots_iterator(xcb_get_setup(g_pWindowManager->DisplayConnection)).data; 45 | 46 | if (!g_pWindowManager->Screen) { 47 | Debug::log(CRIT, "Screen was null!"); 48 | return 1; 49 | } 50 | 51 | // get atoms 52 | for (auto& ATOM : HYPRATOMS) { 53 | xcb_intern_atom_cookie_t cookie = xcb_intern_atom(g_pWindowManager->DisplayConnection, 0, ATOM.first.length(), ATOM.first.c_str()); 54 | xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(g_pWindowManager->DisplayConnection, cookie, NULL); 55 | 56 | if (!reply) { 57 | Debug::log(ERR, "Atom failed: " + ATOM.first); 58 | continue; 59 | } 60 | 61 | ATOM.second = reply->atom; 62 | } 63 | 64 | g_pWindowManager->setupManager(); 65 | 66 | Debug::log(LOG, "Hypr Started!"); 67 | 68 | while (g_pWindowManager->handleEvent()) { 69 | ; 70 | } 71 | 72 | Debug::log(LOG, "Hypr reached the end! Exiting..."); 73 | 74 | xcb_disconnect(g_pWindowManager->DisplayConnection); 75 | 76 | if (const auto err = xcb_connection_has_error(g_pWindowManager->DisplayConnection); err != 0) { 77 | Debug::log(CRIT, "Exiting because of error " + std::to_string(err)); 78 | return err; 79 | } 80 | 81 | return 0; 82 | } -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '34 20 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'cpp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | #- name: Autobuild 56 | # uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | - run: | 66 | sudo apt-get update && sudo apt install xcb cmake gcc libgtk-3-dev ninja-build libgtkmm-3.0-dev libxcb-randr0 libxcb-randr0-dev libxcb-util-dev libxcb-util0-dev libxcb-util1 libxcb-ewmh-dev libxcb-xinerama0 libxcb-xinerama0-dev libxcb-icccm4-dev libxcb-keysyms1-dev libxcb-cursor-dev libxcb-shape0-dev 67 | make clear 68 | make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /src/defines.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "./helpers/Vector.hpp" 36 | #include "./utilities/Debug.hpp" 37 | 38 | #ifndef NDEBUG 39 | #define ISDEBUG true 40 | #else 41 | #define ISDEBUG false 42 | #endif 43 | 44 | // hints 45 | #define NONMOVABLE 46 | #define NONCOPYABLE 47 | // 48 | 49 | #define EXPOSED_MEMBER(var, type, prefix) \ 50 | private: \ 51 | type m_##prefix##var; \ 52 | public: \ 53 | inline type get##var() { return m_##prefix##var; } \ 54 | void set##var(type value) { m_##prefix##var = value; } 55 | 56 | 57 | #define EVENT(name) \ 58 | void event##name(xcb_generic_event_t* event); 59 | 60 | #define STICKS(a, b) abs((a) - (b)) < 2 61 | 62 | #define VECINRECT(vec, x1, y1, x2, y2) (vec.x >= (x1) && vec.x <= (x2) && vec.y >= (y1) && vec.y <= (y2)) 63 | 64 | #define XCBQUERYCHECK(name, query, errormsg) \ 65 | xcb_generic_error_t* error##name; \ 66 | const auto name = query; \ 67 | \ 68 | if (error##name != NULL) { \ 69 | Debug::log(ERR, errormsg); \ 70 | free(error##name); \ 71 | free(name); \ 72 | return; \ 73 | } \ 74 | free(error##name); 75 | 76 | 77 | #define VECTORDELTANONZERO(veca, vecb) (abs(veca.x - vecb.x) > 0.4f || abs(veca.y - vecb.y) > 0.4f) 78 | #define VECTORDELTAMORETHAN(veca, vecb, delta) (abs(veca.x - vecb.x) > (delta) || abs(veca.y - vecb.y) > (delta)) 79 | 80 | #define PROP(cookie, name, len) const auto cookie = xcb_get_property(DisplayConnection, false, window, name, XCB_GET_PROPERTY_TYPE_ANY, 0, len); \ 81 | const auto cookie##reply = xcb_get_property_reply(DisplayConnection, cookie, NULL) 82 | 83 | 84 | 85 | #define HYPRATOM(name) {name, 0} 86 | 87 | #define ALPHA(c) ((double)(((c) >> 24) & 0xff) / 255.0) 88 | #define RED(c) ((double)(((c) >> 16) & 0xff) / 255.0) 89 | #define GREEN(c) ((double)(((c) >> 8) & 0xff) / 255.0) 90 | #define BLUE(c) ((double)(((c)) & 0xff) / 255.0) 91 | 92 | #define CONTAINS(s, f) s.find(f) != std::string::npos 93 | 94 | #define RETURNIFBAR if (g_pWindowManager->statusBar) return; 95 | #define RETURNIFMAIN if (!g_pWindowManager->statusBar) return; 96 | 97 | #define COLORDELTAOVERX(c, c1, d) (abs(RED(c) - RED(c1)) > d / 255.f || abs(GREEN(c) - GREEN(c1)) > d / 255.f || abs(BLUE(c) - BLUE(c1)) > d / 255.f || abs(ALPHA(c) - ALPHA(c1)) > d / 255.f) 98 | 99 | #define _NET_MOVERESIZE_WINDOW_X (1 << 8) 100 | #define _NET_MOVERESIZE_WINDOW_Y (1 << 9) 101 | #define _NET_MOVERESIZE_WINDOW_WIDTH (1 << 10) 102 | #define _NET_MOVERESIZE_WINDOW_HEIGHT (1 << 11) 103 | 104 | #define SCRATCHPAD_ID 1337420 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 |

14 | Hypr is a dynamic Linux tiling window manager for Xorg. It's written in XCB with modern C++ and aims to provide easily readable and expandable code. 15 |

16 | 17 | For Hypr with `land`, see [Hyprland](https://github.com/vaxerski/Hyprland), the Wayland Compositor. 18 | 19 |
20 | Hypr is _only_ a window manager. It is not a compositor and does not implement a compositor's functionality. You can run it without one (e.g. Picom) though, since it runs on Xorg, which doesn't require a compositor. 21 |
22 | 23 | # Key Features 24 | - True parabolic animations 25 | - Rounded corners and borders 26 | - Config reloaded instantly upon saving 27 | - A built-in status bar with modules 28 | - Easily expandable and readable codebase 29 | - Pseudotiling 30 | - Multiple tiling modes (dwindling and master) 31 | - Window rules 32 | - Intelligent transients 33 | - Support for EWMH-compatible bars (e.g. Polybar) 34 | - Keybinds config 35 | - Tiling windows 36 | - Floating windows 37 | - Workspaces 38 | - Moving / Fullscreening windows 39 | - Mostly EWMH and ICCCM compliant 40 | 41 | # Installation 42 | I do not maintain any packages, but some kind people have made them for me. If I missed any, please let me know. 43 | 44 | IMPORTANT: Hypr **requires** xmodmap to correctly apply keybinds. Make sure you have it installed. 45 | 46 | For stable releases, use the Releases tab here on github, and follow the instructions to install it in the [Wiki](https://github.com/vaxerski/Hypr/wiki/Building) 47 | 48 | *Arch (AUR)* 49 | ``` 50 | yay -S hypr-git 51 | ``` 52 | 53 | *Void Linux* 54 | 55 | [https://github.com/Flammable-Duck/hypr-template](https://github.com/Flammable-Duck/hypr-template) 56 | 57 | ## Manual building 58 | If your distro doesn't have Hypr in its repositories, or you want to modify hypr, 59 | 60 | see the [Wiki](https://github.com/vaxerski/Hypr/wiki/Building) to see build and installation instructions. 61 | 62 | # Configuring 63 | See the [Wiki Page](https://github.com/vaxerski/Hypr/wiki/Configuring-Hypr) for a detailed overview on the config, or refer to the example config in examples/hypr.conf. 64 | 65 | You have to use a config, place it in ~/.config/hypr/hypr.conf 66 | 67 | # Screenshot Gallery 68 | 69 | ![One](https://i.imgur.com/ygked0M.png) 70 | ![Two](https://i.imgur.com/HLukmeA.png) 71 | ![Three](https://i.imgur.com/B0MDTu2.png) 72 | 73 | # Known issues 74 | - Picom's shadow and effects do not update for cheap animations while animating 75 | - Non-cheap animations are choppy (duh!) 76 | 77 | # Contributions 78 | Refer to [CONTRIBUTING.md](https://github.com/vaxerski/Hypr/blob/main/CONTRIBUTING.md) and the [Wiki](https://github.com/vaxerski/Hypr/wiki/Contributing-&-Debugging) for contributing instructions and guidelines. 79 | 80 | 81 | # Stars over time 82 | 83 | [![Stars over time](https://starchart.cc/vaxerski/Hypr.svg)](https://starchart.cc/vaxerski/Hypr) 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/bar/Bar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../defines.hpp" 6 | #include "../ipc/ipc.hpp" 7 | #include "BarCommands.hpp" 8 | #include 9 | #include 10 | #include 11 | 12 | inline int barScreen = 0; 13 | 14 | struct SDrawingContext { 15 | xcb_gcontext_t GContext; 16 | xcb_font_t Font; 17 | }; 18 | 19 | enum ModuleAlignment { 20 | LEFT = 0, 21 | CENTER, 22 | RIGHT 23 | }; 24 | 25 | NONMOVABLE NONCOPYABLE struct SBarModule { 26 | ModuleAlignment alignment; 27 | std::string value; 28 | std::string icon; 29 | std::string valueCalculated = ""; 30 | uint64_t color; 31 | uint64_t bgcolor; 32 | 33 | uint64_t updateEveryMs; 34 | std::chrono::system_clock::time_point updateLast; 35 | 36 | xcb_gcontext_t bgcontext = 0; // deprecated 37 | 38 | // PADS 39 | bool isPad = false; 40 | int pad = 0; 41 | 42 | 43 | // Simple but working thread safe value accessor 44 | std::mutex mtx; 45 | std::string accessValueCalculated(bool write, std::string val = "") { 46 | std::lock_guard lg(mtx); 47 | 48 | if (write) 49 | valueCalculated = val; 50 | else 51 | return valueCalculated; 52 | return ""; 53 | } 54 | }; 55 | 56 | class CStatusBar { 57 | public: 58 | CStatusBar() { 59 | m_bIsDestroyed = true; 60 | } 61 | 62 | EXPOSED_MEMBER(WindowID, xcb_window_t, i); 63 | EXPOSED_MEMBER(MonitorID, int, i); 64 | EXPOSED_MEMBER(StatusCommand, std::string, sz); // TODO: make the bar better 65 | EXPOSED_MEMBER(LastWindowName, std::string, sz); 66 | EXPOSED_MEMBER(LastWindowClass, std::string, sz); 67 | EXPOSED_MEMBER(HasTray, bool, b); 68 | EXPOSED_MEMBER(IsDestroyed, bool, b); // for not deleting nulls 69 | 70 | void draw(); 71 | void setup(int MonitorID); 72 | void destroy(); 73 | void setupModule(SBarModule*); 74 | void destroyModule(SBarModule*); 75 | void ensureTrayClientDead(xcb_window_t); 76 | void ensureTrayClientHidden(xcb_window_t, bool); 77 | void setupTray(); 78 | 79 | std::vector openWorkspaces; 80 | EXPOSED_MEMBER(CurrentWorkspace, int, i); 81 | 82 | std::vector modules; 83 | 84 | xcb_window_t trayWindowID = 0; 85 | 86 | private: 87 | 88 | Vector2D m_vecSize; 89 | Vector2D m_vecPosition; 90 | 91 | xcb_pixmap_t m_iPixmap; 92 | 93 | 94 | // Cairo 95 | 96 | cairo_surface_t* m_pCairoSurface = nullptr; 97 | cairo_t* m_pCairo = nullptr; 98 | 99 | void drawText(Vector2D, std::string, uint32_t, std::string, double); 100 | void drawCairoRectangle(Vector2D, Vector2D, uint32_t); 101 | int getTextWidth(std::string, std::string, double); 102 | int drawModule(SBarModule*, int); 103 | int drawWorkspacesModule(SBarModule*, int); 104 | int getTextHalfY(); 105 | void drawErrorScreen(); 106 | 107 | std::unordered_map m_mContexts; 108 | 109 | 110 | void fixTrayOnCreate(); 111 | void saveTrayOnDestroy(); 112 | int drawTrayModule(SBarModule*, int); 113 | }; 114 | 115 | // Main thread for the bar. Is only initted once in main.cpp so we can do this. 116 | int64_t barMainThread(); -------------------------------------------------------------------------------- /example/hypr.conf: -------------------------------------------------------------------------------- 1 | # Hypr example config file 2 | # 3 | # 4 | # =) 5 | 6 | gaps_in=5 7 | border_size=1 8 | gaps_out=20 9 | rounding=0 10 | max_fps=60 # max fps for updates of config & animations 11 | focus_when_hover=1 # 0 - do not switch the focus when hover (only for tiling) 12 | main_mod=SUPER # For moving, resizing 13 | intelligent_transients=1 # keeps transients always on top. 14 | no_unmap_saving=1 # disables saving unmapped windows (seems to break sometimes) 15 | scratchpad_mon=0 # self-explanatory 16 | 17 | # Execs 18 | # exec-once=/home/me/MyEpicShellScript # will exec the script only when the WM launches 19 | # exec=/home/me/MyEpicShellScript # will exec the script every time the config is reloaded 20 | 21 | # Layout 22 | layout=0 # 0 - dwindle (default), 1 - master 23 | layout { 24 | no_gaps_when_only=0 # disables gaps and borders when only window on screen 25 | } 26 | 27 | # Bar config 28 | Bar { 29 | height=20 30 | monitor=0 31 | enabled=1 32 | mod_pad_in=8 33 | no_tray_saving=1 # using this doesnt save the tray between reloads but fixes an issue with the bar disappearing. 34 | 35 | font.main=Noto Sans 36 | font.secondary=Noto Sans 37 | 38 | col.bg=0xff111111 39 | col.high=0xffff3333 40 | 41 | module=left,X,0xff8000ff,0xffffffff,1,workspaces 42 | module=pad,left,10 43 | module=left,,0xff7000dd,0xff7000dd,1,tray 44 | module=right,X,0xffffffff,0xff00ff33,1000,$date +%a,\ %b\ %Y\ \ %I:%M\ %p$ 45 | } 46 | 47 | # colors 48 | col.active_border=0x77ff3333 49 | col.inactive_border=0x77222222 50 | 51 | # status command 52 | # deprecated 53 | # status_command=date +%a,\ %b\ %Y\ \ %I:%M\ %p 54 | # 55 | 56 | # animations 57 | Animations { 58 | enabled=1 # For windows 59 | window_resize_speed=5 # This is for windows resizing 60 | workspaces=1 # For workspace animations (fixed, enabling by default) 61 | speed=5 # This is for workspaces 62 | cheap=1 # highly recommended 63 | borders=0 64 | } 65 | 66 | # example window rules, more in the wiki 67 | 68 | # windowrule=float,class:krunner 69 | # windowrule=float,role:pop-up 70 | # windowrule=float,role:task_dialog 71 | # windowrule=monitor 0,class:krunner 72 | # windowrule=size 500 50,class:krunner 73 | # windowrule=move 700 500,class:krunner 74 | # windowrule=pseudo,class:discord 75 | 76 | # keybinds 77 | bind=SUPER,R,exec,dmenu_run 78 | bind=SUPER,Q,exec,kitty 79 | bind=SUPER,RETURN,exec,xterm 80 | bind=SUPER,G,exec,google-chrome-stable 81 | 82 | bind=SUPER,C,killactive, 83 | 84 | bind=SUPER,LEFT,movewindow,l 85 | bind=SUPER,RIGHT,movewindow,r 86 | bind=SUPER,UP,movewindow,u 87 | bind=SUPER,DOWN,movewindow,d 88 | 89 | bind=SUPER,LEFT,movefocus,l 90 | bind=SUPER,RIGHT,movefocus,r 91 | bind=SUPER,UP,movefocus,u 92 | bind=SUPER,DOWN,movefocus,d 93 | 94 | bind=SUPER,F,fullscreen, 95 | 96 | bind=SUPER,1,workspace,1 97 | bind=SUPER,2,workspace,2 98 | bind=SUPER,3,workspace,3 99 | bind=SUPER,4,workspace,4 100 | bind=SUPER,5,workspace,5 101 | bind=SUPER,6,workspace,6 102 | bind=SUPER,7,workspace,7 103 | bind=SUPER,8,workspace,8 104 | bind=SUPER,9,workspace,9 105 | 106 | bind=SUPERSHIFT,1,movetoworkspace,1 107 | bind=SUPERSHIFT,2,movetoworkspace,2 108 | bind=SUPERSHIFT,3,movetoworkspace,3 109 | bind=SUPERSHIFT,4,movetoworkspace,4 110 | bind=SUPERSHIFT,5,movetoworkspace,5 111 | bind=SUPERSHIFT,6,movetoworkspace,6 112 | bind=SUPERSHIFT,7,movetoworkspace,7 113 | bind=SUPERSHIFT,8,movetoworkspace,8 114 | bind=SUPERSHIFT,9,movetoworkspace,9 115 | 116 | bind=SUPERSHIFT,S,movetoworkspace,scratchpad 117 | bind=SUPER,S,scratchpad, 118 | 119 | bind=SUPER,SPACE,togglefloating, 120 | 121 | bind=SUPER,equal,splitratio,+ 122 | bind=SUPER,minus,splitratio,- 123 | -------------------------------------------------------------------------------- /src/utilities/Util.cpp: -------------------------------------------------------------------------------- 1 | #include "Util.hpp" 2 | #include "../windowManager.hpp" 3 | 4 | // Execute a shell command and get the output 5 | std::string exec(const char* cmd) { 6 | std::array buffer; 7 | std::string result; 8 | const std::unique_ptr pipe(popen(cmd, "r"), pclose); 9 | if (!pipe) { 10 | Debug::log(ERR, "Exec failed in pipe."); 11 | return ""; 12 | } 13 | while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { 14 | result += buffer.data(); 15 | } 16 | return result; 17 | } 18 | 19 | void clearLogs() { 20 | std::ofstream logs; 21 | const std::string DEBUGPATH = "/tmp/hypr/hypr.log"; 22 | const std::string DEBUGPATH2 = "/tmp/hypr/hyprd.log"; 23 | unlink(DEBUGPATH2.c_str()); 24 | unlink(DEBUGPATH.c_str()); 25 | } 26 | 27 | double parabolic(double from, double to, double incline) { 28 | return from + ((to - from) / incline); 29 | } 30 | 31 | CFloatingColor parabolicColor(CFloatingColor from, uint32_t to, double incline) { 32 | from.r = parabolic(from.r, RED(to) * 255.f, incline); 33 | from.g = parabolic(from.g, GREEN(to) * 255.f, incline); 34 | from.b = parabolic(from.b, BLUE(to) * 255.f, incline); 35 | from.a = parabolic(from.a, ALPHA(to) * 255.f, incline); 36 | 37 | return from; 38 | } 39 | 40 | CFloatingColor parabolicColor(CFloatingColor from, CFloatingColor to, double incline) { 41 | from.r = parabolic(from.r, to.r, incline); 42 | from.g = parabolic(from.g, to.g, incline); 43 | from.b = parabolic(from.b, to.b, incline); 44 | from.a = parabolic(from.a, to.a, incline); 45 | 46 | return from; 47 | } 48 | 49 | void emptyEvent(xcb_drawable_t window) { 50 | xcb_expose_event_t exposeEvent; 51 | exposeEvent.window = window; 52 | exposeEvent.response_type = 0; 53 | exposeEvent.x = 0; 54 | exposeEvent.y = 0; 55 | exposeEvent.width = g_pWindowManager->Screen->width_in_pixels; 56 | exposeEvent.height = g_pWindowManager->Screen->height_in_pixels; 57 | xcb_send_event(g_pWindowManager->DisplayConnection, false, window, XCB_EVENT_MASK_EXPOSURE, (char*)&exposeEvent); 58 | xcb_flush(g_pWindowManager->DisplayConnection); 59 | } 60 | 61 | void wakeUpEvent(xcb_drawable_t window) { 62 | const auto PWINDOW = g_pWindowManager->getWindowFromDrawable(window); 63 | 64 | if (!PWINDOW) 65 | return; 66 | 67 | PWINDOW->setRealPosition(PWINDOW->getRealPosition() + Vector2D(1, 1)); 68 | PWINDOW->setDirty(true); 69 | 70 | g_pWindowManager->refreshDirtyWindows(); 71 | 72 | xcb_flush(g_pWindowManager->DisplayConnection); 73 | 74 | PWINDOW->setRealPosition(PWINDOW->getRealPosition() - Vector2D(1, 1)); 75 | PWINDOW->setDirty(true); 76 | } 77 | 78 | bool xcbContainsAtom(xcb_get_property_reply_t* PROP, xcb_atom_t ATOM) { 79 | if (PROP == NULL || xcb_get_property_value_length(PROP) == 0) 80 | return false; 81 | 82 | const auto ATOMS = (xcb_atom_t*)xcb_get_property_value(PROP); 83 | if (!ATOMS) 84 | return false; 85 | 86 | for (int i = 0; i < xcb_get_property_value_length(PROP) / (PROP->format / 8); ++i) 87 | if (ATOMS[i] == ATOM) 88 | return true; 89 | 90 | return false; 91 | } 92 | 93 | std::vector splitString(std::string in, char c) { 94 | std::vector returns; 95 | 96 | while(in.length() > 0) { 97 | std::string toPush = in.substr(0, in.find_first_of(c)); 98 | if (toPush != "") { 99 | returns.push_back(toPush); 100 | } 101 | 102 | if (in.find_first_of(c) != std::string::npos) 103 | in = in.substr(in.find_first_of(c) + 1); 104 | else 105 | in = ""; 106 | } 107 | 108 | return returns; 109 | } -------------------------------------------------------------------------------- /src/window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defines.hpp" 4 | #include "utilities/Workspace.hpp" 5 | #include "utilities/Util.hpp" 6 | 7 | class CWindow { 8 | public: 9 | CWindow(); 10 | ~CWindow(); 11 | 12 | void move(Vector2D dest); 13 | void moveByDelta(Vector2D delta); 14 | 15 | void resize(Vector2D size); 16 | void resize(float percx, float percy); 17 | 18 | // ------------------------------------- // 19 | // Node Stuff // 20 | // ------------------------------------- // 21 | 22 | // IDs: 23 | // > 0 : Windows 24 | // == 0 : None 25 | // < 0 : Nodes 26 | 27 | EXPOSED_MEMBER(ParentNodeID, int64_t, i); 28 | 29 | EXPOSED_MEMBER(ChildNodeAID, int64_t, i); 30 | EXPOSED_MEMBER(ChildNodeBID, int64_t, i); 31 | 32 | void generateNodeID(); 33 | 34 | // ------------------------------------- // 35 | 36 | EXPOSED_MEMBER(Name, std::string, sz); 37 | EXPOSED_MEMBER(ClassName, std::string, sz); 38 | EXPOSED_MEMBER(RoleName, std::string, sz); 39 | EXPOSED_MEMBER(Constructed, bool, b); 40 | EXPOSED_MEMBER(FirstOpen, bool, b); 41 | 42 | // Tells the window manager to reload the window's params 43 | EXPOSED_MEMBER(Dirty, bool, b); 44 | 45 | void bringTopRecursiveTransients(); 46 | void setDirtyRecursive(bool); 47 | void addTransientChild(xcb_drawable_t); 48 | // ONLY for dwindle layout! 49 | void recalcSizePosRecursive(); 50 | 51 | EXPOSED_MEMBER(Size, Vector2D, vec); 52 | EXPOSED_MEMBER(EffectiveSize, Vector2D, vec); 53 | EXPOSED_MEMBER(EffectivePosition, Vector2D, vec); 54 | EXPOSED_MEMBER(Position, Vector2D, vec); 55 | EXPOSED_MEMBER(RealSize, Vector2D, vec); 56 | EXPOSED_MEMBER(RealPosition, Vector2D, vec); 57 | EXPOSED_MEMBER(LastUpdateSize, Vector2D, vec); 58 | EXPOSED_MEMBER(LastUpdatePosition, Vector2D, vec); 59 | EXPOSED_MEMBER(IsFloating, bool, b); 60 | EXPOSED_MEMBER(Drawable, int64_t, i); // int64_t because it's my internal ID system too. 61 | 62 | // For splitting ratios 63 | EXPOSED_MEMBER(SplitRatio, float, f); 64 | 65 | // Fullscreen 66 | EXPOSED_MEMBER(Fullscreen, bool, b); 67 | 68 | // Workspace pointer 69 | EXPOSED_MEMBER(WorkspaceID, int, i); 70 | 71 | // For floating 72 | EXPOSED_MEMBER(DefaultSize, Vector2D, vec); 73 | EXPOSED_MEMBER(DefaultPosition, Vector2D, vec); 74 | EXPOSED_MEMBER(UnderFullscreen, bool, b); 75 | 76 | // Monitors 77 | EXPOSED_MEMBER(Monitor, int, i); 78 | 79 | // Docks etc 80 | EXPOSED_MEMBER(Immovable, bool, b); 81 | EXPOSED_MEMBER(NoInterventions, bool, b); 82 | 83 | // ICCCM 84 | EXPOSED_MEMBER(CanKill, bool, b); 85 | 86 | // Master layout 87 | EXPOSED_MEMBER(Master, bool, b); 88 | EXPOSED_MEMBER(MasterChildIndex, int, i); 89 | EXPOSED_MEMBER(Dead, bool, b); 90 | 91 | // Animating cheaply 92 | EXPOSED_MEMBER(IsAnimated, bool, b); 93 | EXPOSED_MEMBER(FirstAnimFrame, bool, b); 94 | 95 | // Weird shenaningans 96 | EXPOSED_MEMBER(IsSleeping, bool, b); 97 | 98 | // Animate borders 99 | EXPOSED_MEMBER(RealBorderColor, CFloatingColor, c); 100 | EXPOSED_MEMBER(EffectiveBorderColor, CFloatingColor, c); 101 | 102 | // Docks 103 | EXPOSED_MEMBER(Dock, bool, b); 104 | EXPOSED_MEMBER(DockAlign, EDockAlign, e); 105 | EXPOSED_MEMBER(DockHidden, bool, b); 106 | 107 | // Transient 108 | EXPOSED_MEMBER(Children, std::vector, vec); 109 | EXPOSED_MEMBER(Transient, bool, b); 110 | 111 | // Pseudotiling 112 | EXPOSED_MEMBER(IsPseudotiled, bool, b); 113 | EXPOSED_MEMBER(PseudoSize, Vector2D, vec); 114 | 115 | // For dragging tiled windows 116 | EXPOSED_MEMBER(DraggingTiled, bool, b); 117 | 118 | // For pinning floating 119 | EXPOSED_MEMBER(Pinned, bool, b); 120 | 121 | private: 122 | 123 | }; -------------------------------------------------------------------------------- /src/window.cpp: -------------------------------------------------------------------------------- 1 | #include "window.hpp" 2 | #include "windowManager.hpp" 3 | 4 | CWindow::CWindow() { this->setPinned(false); this->setDraggingTiled(false); this->setIsPseudotiled(false); this->setSplitRatio(1); this->setDockHidden(false); this->setRealBorderColor(0); this->setEffectiveBorderColor(0); this->setFirstOpen(true); this->setConstructed(false); this->setTransient(false); this->setLastUpdatePosition(Vector2D(0,0)); this->setLastUpdateSize(Vector2D(0,0)); this->setDock(false); this->setUnderFullscreen(false); this->setIsSleeping(true); this->setFirstAnimFrame(true); this->setIsAnimated(false); this->setDead(false); this->setMasterChildIndex(0); this->setMaster(false); this->setCanKill(false); this->setImmovable(false); this->setNoInterventions(false); this->setDirty(true); this->setFullscreen(false); this->setIsFloating(false); this->setParentNodeID(0); this->setChildNodeAID(0); this->setChildNodeBID(0); this->setName(""); } 5 | CWindow::~CWindow() { } 6 | 7 | void CWindow::generateNodeID() { 8 | 9 | int64_t lowest = -1; 10 | for (auto& window : g_pWindowManager->windows) { 11 | if ((int64_t)window.getDrawable() < lowest) { 12 | lowest = (int64_t)window.getDrawable(); 13 | } 14 | } 15 | 16 | m_iDrawable = lowest - 1; 17 | } 18 | 19 | void CWindow::setDirtyRecursive(bool val) { 20 | m_bDirty = val; 21 | 22 | if (m_iChildNodeAID != 0) { 23 | g_pWindowManager->getWindowFromDrawable(m_iChildNodeAID)->setDirtyRecursive(val); 24 | g_pWindowManager->getWindowFromDrawable(m_iChildNodeBID)->setDirtyRecursive(val); 25 | } 26 | } 27 | 28 | void CWindow::recalcSizePosRecursive() { 29 | if (m_iChildNodeAID != 0) { 30 | const auto HORIZONTAL = m_vecSize.x > m_vecSize.y; 31 | 32 | const auto REVERSESPLITRATIO = 2.f - m_fSplitRatio; 33 | 34 | g_pWindowManager->getWindowFromDrawable(m_iChildNodeAID)->setPosition(m_vecPosition); 35 | g_pWindowManager->getWindowFromDrawable(m_iChildNodeBID)->setPosition(m_vecPosition + (HORIZONTAL ? Vector2D(m_vecSize.x / 2.f * m_fSplitRatio, 0) : Vector2D(0, m_vecSize.y / 2.f * m_fSplitRatio))); 36 | 37 | g_pWindowManager->getWindowFromDrawable(m_iChildNodeAID)->setSize(Vector2D(m_vecSize.x / (HORIZONTAL ? 2 / m_fSplitRatio : 1), m_vecSize.y / (HORIZONTAL ? 1 : 2 / m_fSplitRatio))); 38 | g_pWindowManager->getWindowFromDrawable(m_iChildNodeBID)->setSize(Vector2D(m_vecSize.x / (HORIZONTAL ? 2 / REVERSESPLITRATIO : 1), m_vecSize.y / (HORIZONTAL ? 1 : 2 / REVERSESPLITRATIO))); 39 | 40 | g_pWindowManager->getWindowFromDrawable(m_iChildNodeAID)->setDirty(true); 41 | g_pWindowManager->getWindowFromDrawable(m_iChildNodeBID)->setDirty(true); 42 | 43 | if (m_iChildNodeAID < 0) { 44 | g_pWindowManager->getWindowFromDrawable(m_iChildNodeAID)->recalcSizePosRecursive(); 45 | } 46 | 47 | if (m_iChildNodeBID < 0) { 48 | g_pWindowManager->getWindowFromDrawable(m_iChildNodeBID)->recalcSizePosRecursive(); 49 | } 50 | } 51 | } 52 | 53 | void CWindow::bringTopRecursiveTransients() { 54 | // check if its enabled 55 | if (ConfigManager::getInt("intelligent_transients") != 1) 56 | return; 57 | 58 | // if this is a floating window, top 59 | if (m_bIsFloating && m_iDrawable > 0) 60 | g_pWindowManager->setAWindowTop(m_iDrawable); 61 | 62 | // first top all the children if floating 63 | for (auto& c : m_vecChildren) { 64 | if (const auto PWINDOW = g_pWindowManager->getWindowFromDrawable(c); PWINDOW) { 65 | if (PWINDOW->getIsFloating()) 66 | g_pWindowManager->setAWindowTop(c); 67 | } 68 | } 69 | 70 | // THEN top their children 71 | for (auto& c : m_vecChildren) { 72 | if (const auto PCHILD = g_pWindowManager->getWindowFromDrawable(c); PCHILD) { 73 | // recurse 74 | PCHILD->bringTopRecursiveTransients(); 75 | } 76 | } 77 | } 78 | 79 | void CWindow::addTransientChild(xcb_window_t w) { 80 | m_vecChildren.push_back(w); 81 | } -------------------------------------------------------------------------------- /src/utilities/XCBProps.cpp: -------------------------------------------------------------------------------- 1 | #include "XCBProps.hpp" 2 | 3 | #include "../windowManager.hpp" 4 | 5 | #define DisplayConnection g_pWindowManager->DisplayConnection 6 | 7 | std::pair getClassName(int64_t window) { 8 | PROP(class_cookie, XCB_ATOM_WM_CLASS, 128); 9 | 10 | if (!class_cookiereply) { 11 | return std::make_pair<>("Error", "Error"); 12 | } 13 | 14 | const size_t PROPLEN = xcb_get_property_value_length(class_cookiereply); 15 | char* NEWCLASS = (char*)xcb_get_property_value(class_cookiereply); 16 | const size_t CLASSNAMEINDEX = strnlen(NEWCLASS, PROPLEN) + 1; 17 | 18 | char* CLASSINSTANCE = strndup(NEWCLASS, PROPLEN); 19 | char* CLASSNAME; 20 | bool freeClassName = true; 21 | if (CLASSNAMEINDEX < PROPLEN) { 22 | CLASSNAME = strndup(NEWCLASS + CLASSNAMEINDEX, PROPLEN - CLASSNAMEINDEX); 23 | } else { 24 | CLASSNAME = ""; 25 | freeClassName = false; 26 | } 27 | 28 | std::string CLASSINST(CLASSINSTANCE); 29 | std::string CLASSNAM(CLASSNAME); 30 | 31 | free(class_cookiereply); 32 | free(CLASSINSTANCE); 33 | if (freeClassName) 34 | free(CLASSNAME); 35 | 36 | return std::make_pair<>(CLASSINST, CLASSNAM); 37 | } 38 | 39 | std::string getRoleName(int64_t window) { 40 | PROP(role_cookie, HYPRATOMS["WM_WINDOW_ROLE"], 128); 41 | 42 | if (!role_cookiereply) 43 | return "Error"; 44 | 45 | std::string returns = ""; 46 | 47 | if (role_cookiereply == NULL || xcb_get_property_value_length(role_cookiereply)) { 48 | Debug::log(ERR, "Role reply was invalid!"); 49 | } else { 50 | // get the role 51 | 52 | char* role; 53 | asprintf(&role, "%.*s", xcb_get_property_value_length(role_cookiereply), (char*)xcb_get_property_value(role_cookiereply)); 54 | 55 | returns = role; 56 | 57 | free(role); 58 | } 59 | 60 | free(role_cookiereply); 61 | 62 | return returns; 63 | } 64 | 65 | std::string getWindowName(uint64_t window) { 66 | PROP(name_cookie, HYPRATOMS["_NET_WM_NAME"], 128); 67 | 68 | if (!name_cookiereply) 69 | return "Error"; 70 | 71 | const int len = xcb_get_property_value_length(name_cookiereply); 72 | char* name = strndup((const char*)xcb_get_property_value(name_cookiereply), len); 73 | std::string stringname(name); 74 | free(name); 75 | 76 | free(name_cookiereply); 77 | 78 | return stringname; 79 | } 80 | 81 | void removeAtom(const int& window, xcb_atom_t prop, xcb_atom_t atom) { 82 | xcb_grab_server(DisplayConnection); 83 | 84 | const auto REPLY = xcb_get_property_reply(DisplayConnection, xcb_get_property(DisplayConnection, false, window, prop, XCB_GET_PROPERTY_TYPE_ANY, 0, 4096), NULL); 85 | 86 | if (!REPLY || xcb_get_property_value_length(REPLY) == 0) { 87 | free(REPLY); 88 | xcb_ungrab_server(DisplayConnection); 89 | 90 | return; 91 | } 92 | 93 | xcb_atom_t* atomsList = (xcb_atom_t*)xcb_get_property_value(REPLY); 94 | if (!atomsList) { 95 | free(REPLY); 96 | xcb_ungrab_server(DisplayConnection); 97 | 98 | return; 99 | } 100 | 101 | int valuesnum = 0; 102 | const int current_size = xcb_get_property_value_length(REPLY) / (REPLY->format / 8); 103 | xcb_atom_t values[current_size]; 104 | for (int i = 0; i < current_size; i++) { 105 | if (atomsList[i] != atom) 106 | values[valuesnum++] = atomsList[i]; 107 | } 108 | 109 | xcb_change_property(DisplayConnection, XCB_PROP_MODE_REPLACE, window, prop, XCB_ATOM_ATOM, 32, valuesnum, values); 110 | 111 | free(REPLY); 112 | xcb_ungrab_server(DisplayConnection); 113 | } 114 | 115 | uint8_t getWindowState(const int& win) { 116 | uint32_t returns = 0; 117 | 118 | const auto COOKIE = xcb_get_property(DisplayConnection, 0, win, HYPRATOMS["_NET_WM_STATE"], HYPRATOMS["_NET_WM_STATE"], 0L, 2L); 119 | const auto REPLY = xcb_get_property_reply(DisplayConnection, COOKIE, NULL); 120 | if (REPLY) { 121 | if (REPLY->type == HYPRATOMS["_NET_WM_STATE"] && REPLY->format == 32 && REPLY->length == 2) { 122 | returns = *((uint32_t*)xcb_get_property_value(REPLY)); 123 | } 124 | 125 | free(REPLY); 126 | } 127 | return returns; 128 | } -------------------------------------------------------------------------------- /src/utilities/AnimationUtil.cpp: -------------------------------------------------------------------------------- 1 | #include "AnimationUtil.hpp" 2 | #include "../windowManager.hpp" 3 | 4 | void AnimationUtil::move() { 5 | 6 | static std::chrono::time_point lastFrame = std::chrono::high_resolution_clock::now(); 7 | const double DELTA = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - lastFrame).count(); 8 | lastFrame = std::chrono::high_resolution_clock::now(); 9 | 10 | const double ANIMATIONSPEED = std::max(1.f / ((double)ConfigManager::getFloat("animations:speed") * DELTA) * 462.f, (double)1.f); 11 | const double RESIZEANIMATIONSPEED = std::max(1.f / ((double)ConfigManager::getFloat("animations:window_resize_speed") * DELTA) * 462.f, (double)1.f); 12 | 13 | bool updateRequired = false; 14 | // Now we are (or should be, lul) thread-safe. 15 | for (auto& window : g_pWindowManager->windows) { 16 | // check if window needs an animation. 17 | window.setIsAnimated(false); 18 | 19 | if (!window.getConstructed()) 20 | continue; 21 | 22 | // Border animations 23 | if (window.getDrawable() > 0) { 24 | if (window.getEffectiveBorderColor().getAsUint32() != window.getRealBorderColor().getAsUint32() /* As uint32 to round and not spam */) { 25 | // interp border color if enabled 26 | const auto PREVCOLOR = window.getRealBorderColor().getAsUint32(); 27 | 28 | if (ConfigManager::getInt("animations:borders") == 1) { 29 | window.setRealBorderColor(parabolicColor(window.getRealBorderColor(), window.getEffectiveBorderColor(), ANIMATIONSPEED)); 30 | } else { 31 | window.setRealBorderColor(window.getEffectiveBorderColor()); 32 | } 33 | 34 | if (COLORDELTAOVERX(PREVCOLOR, window.getRealBorderColor().getAsUint32(), 2)) { 35 | updateRequired = true; 36 | window.setDirty(true); 37 | } 38 | } 39 | } 40 | 41 | if (ConfigManager::getInt("animations:enabled") == 0 || window.getIsFloating()) { 42 | // Disabled animations. instant warps. 43 | 44 | if (VECTORDELTANONZERO(window.getRealPosition(), window.getEffectivePosition()) 45 | || VECTORDELTANONZERO(window.getRealSize(), window.getEffectiveSize())) { 46 | window.setDirty(true); 47 | updateRequired = true; 48 | 49 | window.setRealPosition(window.getEffectivePosition()); 50 | window.setRealSize(window.getEffectiveSize()); 51 | } 52 | 53 | continue; 54 | } 55 | 56 | if (VECTORDELTANONZERO(window.getRealPosition(), window.getEffectivePosition())) { 57 | Debug::log(LOG, "Updating position animations for " + std::to_string(window.getDrawable()) + " delta: " + std::to_string(ANIMATIONSPEED)); 58 | window.setIsAnimated(true); 59 | 60 | // we need to update it. 61 | window.setDirty(true); 62 | updateRequired = true; 63 | 64 | const auto EFFPOS = window.getEffectivePosition(); 65 | const auto REALPOS = window.getRealPosition(); 66 | 67 | window.setRealPosition(Vector2D(parabolic(REALPOS.x, EFFPOS.x, ANIMATIONSPEED), parabolic(REALPOS.y, EFFPOS.y, ANIMATIONSPEED))); 68 | } 69 | 70 | if (VECTORDELTANONZERO(window.getRealSize(), window.getEffectiveSize())) { 71 | Debug::log(LOG, "Updating size animations for " + std::to_string(window.getDrawable()) + " delta: " + std::to_string(ANIMATIONSPEED)); 72 | window.setIsAnimated(true); 73 | 74 | // we need to update it. 75 | window.setDirty(true); 76 | updateRequired = true; 77 | 78 | const auto REALSIZ = window.getRealSize(); 79 | const auto EFFSIZ = window.getEffectiveSize(); 80 | 81 | window.setRealSize(Vector2D(parabolic(REALSIZ.x, EFFSIZ.x, RESIZEANIMATIONSPEED), parabolic(REALSIZ.y, EFFSIZ.y, RESIZEANIMATIONSPEED))); 82 | } 83 | 84 | // set not animated if already done here 85 | if (!VECTORDELTANONZERO(window.getRealPosition(), window.getEffectivePosition()) 86 | && !VECTORDELTANONZERO(window.getRealSize(), window.getEffectiveSize())) { 87 | window.setIsAnimated(false); 88 | 89 | window.setRealSize(window.getEffectiveSize()); 90 | window.setRealPosition(window.getEffectivePosition()); 91 | } 92 | } 93 | 94 | for (auto& work : g_pWindowManager->workspaces) { 95 | work.setAnimationInProgress(false); 96 | if (VECTORDELTANONZERO(work.getCurrentOffset(), work.getGoalOffset())) { 97 | work.setAnimationInProgress(true); 98 | work.setCurrentOffset(Vector2D(parabolic(work.getCurrentOffset().x, work.getGoalOffset().x, ANIMATIONSPEED), parabolic(work.getCurrentOffset().y, work.getGoalOffset().y, ANIMATIONSPEED))); 99 | 100 | updateRequired = true; 101 | g_pWindowManager->setAllWorkspaceWindowsDirtyByID(work.getID()); 102 | 103 | if (ConfigManager::getInt("animations:workspaces") == 0) 104 | work.setAnimationInProgress(false); 105 | } 106 | 107 | if (!work.getAnimationInProgress()) { 108 | if (!g_pWindowManager->isWorkspaceVisible(work.getID())) { 109 | if (work.getCurrentOffset().x != 1500000) { 110 | g_pWindowManager->setAllWorkspaceWindowsDirtyByID(work.getID()); 111 | updateRequired = true; 112 | } 113 | 114 | work.setCurrentOffset(Vector2D(1500000, 1500000)); 115 | work.setGoalOffset(Vector2D(1500000, 1500000)); 116 | } else { 117 | if (work.getCurrentOffset().x != 0) { 118 | g_pWindowManager->setAllWorkspaceWindowsDirtyByID(work.getID()); 119 | updateRequired = true; 120 | } 121 | 122 | work.setCurrentOffset(Vector2D(0, 0)); 123 | work.setGoalOffset(Vector2D(0, 0)); 124 | } 125 | } 126 | } 127 | 128 | if (updateRequired) 129 | emptyEvent(); // send a fake request to update dirty windows 130 | } -------------------------------------------------------------------------------- /src/bar/BarCommands.cpp: -------------------------------------------------------------------------------- 1 | #include "BarCommands.hpp" 2 | #include "../windowManager.hpp" 3 | 4 | std::vector> lastReads; 5 | 6 | std::string getCpuString() { 7 | std::vector> usageRead; 8 | 9 | std::ifstream cpuif; 10 | cpuif.open("/proc/stat"); 11 | 12 | std::string line; 13 | while (std::getline(cpuif, line)) { 14 | // parse line 15 | const auto stats = splitString(line, ' '); 16 | 17 | if (stats.size() < 1) 18 | continue; 19 | 20 | if (stats[0].find("cpu") == std::string::npos) 21 | break; 22 | 23 | // get the percent 24 | try { 25 | const auto user = stol(stats[1]); 26 | const auto nice = stol(stats[2]); 27 | const auto kern = stol(stats[3]); 28 | 29 | // get total 30 | long total = 0; 31 | for (const auto& t : stats) { 32 | if (t.find("c") != std::string::npos) 33 | continue; 34 | 35 | total += stol(t); 36 | } 37 | 38 | usageRead.push_back({user + nice + kern, total}); 39 | } catch( ... ) { ; } // oops 40 | } 41 | 42 | // Compare the values 43 | std::pair lastReadsTotal = {0, 0}; 44 | for (const auto& lr : lastReads) { 45 | lastReadsTotal.first += lr.first; 46 | lastReadsTotal.second += lr.second; 47 | } 48 | 49 | std::pair newReadsTotal = {0, 0}; 50 | for (const auto& nr : usageRead) { 51 | newReadsTotal.first += nr.first; 52 | newReadsTotal.second += nr.second; 53 | } 54 | 55 | std::pair difference = {newReadsTotal.first - lastReadsTotal.first, newReadsTotal.second - lastReadsTotal.second}; 56 | 57 | float percUsage = (float)difference.first / (float)difference.second; 58 | 59 | lastReads.clear(); 60 | 61 | for (const auto& nr : usageRead) { 62 | lastReads.push_back(nr); 63 | } 64 | 65 | return std::to_string((int)(percUsage * 100.f)) + "%"; 66 | } 67 | 68 | std::string getRamString() { 69 | float available = 0; 70 | float total = 0; 71 | 72 | std::ifstream ramif; 73 | ramif.open("/proc/meminfo"); 74 | 75 | std::string line; 76 | while (std::getline(ramif, line)) { 77 | // parse line 78 | const auto KEY = line.substr(0, line.find_first_of(':')); 79 | 80 | int startValue = 0; 81 | for (int i = 0; i < line.length(); ++i) { 82 | const auto& c = line[i]; 83 | if (c >= '0' && c <= '9') { 84 | startValue = i; 85 | break; 86 | } 87 | } 88 | 89 | float VALUE = 0.f; 90 | try { 91 | std::string toVal = line.substr(startValue); 92 | toVal = toVal.substr(0, line.find_first_of(' ', startValue)); 93 | VALUE = stol(toVal); 94 | } catch (...) { ; } // oops 95 | VALUE /= 1024.f; 96 | 97 | if (KEY == "MemTotal") { 98 | total = VALUE; 99 | } else if (KEY == "MemAvailable") { 100 | available = VALUE; 101 | } 102 | } 103 | 104 | return std::to_string((int)(total - available)) + "MB/" + std::to_string((int)total) + "MB"; 105 | } 106 | 107 | std::string getCurrentWindowName() { 108 | return g_pWindowManager->statusBar->getLastWindowName(); 109 | } 110 | 111 | std::string getCurrentWindowClass() { 112 | return g_pWindowManager->statusBar->getLastWindowClass(); 113 | } 114 | 115 | std::string BarCommands::parsePercent(std::string token) { 116 | // check what the token is and act accordingly. 117 | 118 | if (token == "RAM") return getRamString(); 119 | else if (token == "CPU") return getCpuString(); 120 | else if (token == "WINNAME") return getCurrentWindowName(); 121 | else if (token == "WINCLASS") return getCurrentWindowClass(); 122 | 123 | Debug::log(ERR, "Unknown token while parsing module: %" + token + "%"); 124 | 125 | return "Error"; 126 | } 127 | 128 | std::string BarCommands::parseDollar(std::string token) { 129 | const auto result = exec(token.c_str()); 130 | return result.substr(0, result.length() - 1); 131 | } 132 | 133 | std::string BarCommands::parseCommand(std::string command) { 134 | 135 | std::string result = ""; 136 | 137 | for (long unsigned int i = 0; i < command.length(); ++i) { 138 | 139 | const auto c = command[i]; 140 | 141 | if (c == '%') { 142 | // find the next one 143 | for (long unsigned int j = i + 1; i < command.length(); ++j) { 144 | if (command[j] == '%') { 145 | // found! 146 | auto toSend = command.substr(i + 1); 147 | toSend = toSend.substr(0, toSend.find_first_of('%')); 148 | result += parsePercent(toSend); 149 | i = j; 150 | break; 151 | } 152 | 153 | if (command[j] == ' ') 154 | break; // if there is a space it's not a token 155 | } 156 | } 157 | 158 | else if (c == '$') { 159 | // find the next one 160 | for (long unsigned int j = i + 1; i < command.length(); ++j) { 161 | if (command[j] == '$' && command[j - 1] != '\\') { 162 | // found! 163 | auto toSend = command.substr(i + 1, j - (i + 1)); 164 | std::string toSendWithRemovedEscapes = ""; 165 | for (std::size_t k = 0; k < toSend.length(); ++k) { 166 | if (toSend[k] == '\\' && (k + 1) < toSend.length()) { 167 | char next = toSend[k + 1]; 168 | if (next == '$' || next == '{' || next == '}') { 169 | continue; 170 | } 171 | } 172 | toSendWithRemovedEscapes += toSend[k]; 173 | } 174 | result += parseDollar(toSendWithRemovedEscapes); 175 | i = j; 176 | break; 177 | } 178 | 179 | if (j + 1 == command.length()) { 180 | Debug::log(ERR, "Unpaired $ in a module, module command: "); 181 | Debug::log(NONE, command); 182 | } 183 | } 184 | } 185 | 186 | else { 187 | result += command[i]; 188 | } 189 | } 190 | 191 | return result; 192 | } -------------------------------------------------------------------------------- /src/ipc/ipc.cpp: -------------------------------------------------------------------------------- 1 | #include "ipc.hpp" 2 | #include "../windowManager.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | std::string readFromIPCChannel(std::string path) { 10 | std::ifstream is; 11 | is.open(path.c_str()); 12 | 13 | std::string resultString = std::string((std::istreambuf_iterator(is)), std::istreambuf_iterator()); 14 | 15 | is.close(); 16 | return resultString; 17 | }; 18 | 19 | int writeToIPCChannel(const std::string path, std::string text) { 20 | std::ofstream of; 21 | of.open(path, std::ios::trunc); 22 | 23 | of << text; 24 | 25 | of.close(); 26 | return 0; 27 | } 28 | 29 | void IPCSendMessage(const std::string path, SIPCMessageBarToMain smessage) { 30 | if (!g_pWindowManager->statusBar) { 31 | Debug::log(ERR, "Tried to write as a bar from the main thread?!"); 32 | return; 33 | } 34 | 35 | try { 36 | std::string message = ""; 37 | 38 | // write the WID 39 | message += "wid" + IPC_MESSAGE_EQUALITY + std::to_string(smessage.windowID) + IPC_MESSAGE_SEPARATOR; 40 | 41 | // append the EOF 42 | message += IPC_END_OF_FILE; 43 | 44 | writeToIPCChannel(path, message); 45 | } catch (...) { 46 | Debug::log(WARN, "Error in sending Message B!"); 47 | } 48 | } 49 | 50 | void IPCSendMessage(const std::string path, SIPCMessageMainToBar smessage) { 51 | if (g_pWindowManager->statusBar) { 52 | Debug::log(ERR, "Tried to write as main from the bar thread?!"); 53 | return; 54 | } 55 | 56 | try { 57 | std::string message = ""; 58 | 59 | // write active workspace data 60 | message += "active" + IPC_MESSAGE_EQUALITY + std::to_string(smessage.activeWorkspace) + IPC_MESSAGE_SEPARATOR; 61 | 62 | // write workspace data 63 | message += "workspaces" + IPC_MESSAGE_EQUALITY; 64 | for (const auto &w : smessage.openWorkspaces) { 65 | message += std::to_string(w) + ","; 66 | } 67 | 68 | message += IPC_MESSAGE_SEPARATOR + "barfullscreenwindow" + IPC_MESSAGE_EQUALITY + (smessage.fullscreenOnBar ? "1" : "0"); 69 | 70 | message += IPC_MESSAGE_SEPARATOR + "lastwindowname" + IPC_MESSAGE_EQUALITY + smessage.lastWindowName; 71 | 72 | message += IPC_MESSAGE_SEPARATOR + "lastwindowclass" + IPC_MESSAGE_EQUALITY + smessage.lastWindowClass; 73 | 74 | // append the EOF 75 | message += IPC_MESSAGE_SEPARATOR + IPC_END_OF_FILE; 76 | 77 | // Send 78 | writeToIPCChannel(path, message); 79 | } catch (...) { 80 | Debug::log(WARN, "Error in sending Message M!"); 81 | } 82 | } 83 | 84 | void IPCRecieveMessageB(const std::string path) { 85 | // recieve message as bar 86 | 87 | if (!g_pWindowManager->statusBar) { 88 | Debug::log(ERR, "Tried to read as a bar from the main thread?!"); 89 | return; 90 | } 91 | 92 | try { 93 | std::string message = readFromIPCChannel(path); 94 | 95 | const auto EOFPOS = message.find(IPC_END_OF_FILE); 96 | 97 | if (EOFPOS == std::string::npos) 98 | return; 99 | 100 | message = message.substr(0, EOFPOS); 101 | 102 | while (message.find(IPC_MESSAGE_SEPARATOR) != std::string::npos && message.find(IPC_MESSAGE_SEPARATOR) != 0) { 103 | // read until done. 104 | const auto PROP = message.substr(0, message.find(IPC_MESSAGE_SEPARATOR)); 105 | message = message.substr(message.find(IPC_MESSAGE_SEPARATOR) + IPC_MESSAGE_SEPARATOR.length()); 106 | 107 | // Get the name and value 108 | const auto PROPNAME = PROP.substr(0, PROP.find(IPC_MESSAGE_EQUALITY)); 109 | const auto PROPVALUE = PROP.substr(PROP.find(IPC_MESSAGE_EQUALITY) + 1); 110 | 111 | if (PROPNAME == "active") { 112 | try { 113 | g_pWindowManager->statusBar->setCurrentWorkspace(stoi(PROPVALUE)); 114 | } catch (...) { 115 | } // Try Catch because stoi can be weird 116 | } else if (PROPNAME == "workspaces") { 117 | // Read all 118 | auto propvalue = PROPVALUE; 119 | 120 | g_pWindowManager->statusBar->openWorkspaces.clear(); 121 | 122 | while (propvalue.find_first_of(',') != 0 && propvalue.find_first_of(',') != std::string::npos) { 123 | const auto WORKSPACE = propvalue.substr(0, propvalue.find_first_of(',')); 124 | propvalue = propvalue.substr(propvalue.find_first_of(',') + 1); 125 | 126 | try { 127 | g_pWindowManager->statusBar->openWorkspaces.push_back(stoi(WORKSPACE)); 128 | } catch (...) { 129 | } // Try Catch because stoi can be weird 130 | } 131 | 132 | // sort 133 | std::sort(g_pWindowManager->statusBar->openWorkspaces.begin(), g_pWindowManager->statusBar->openWorkspaces.end()); 134 | } else if (PROPNAME == "lastwindowname") { 135 | g_pWindowManager->statusBar->setLastWindowName(PROPVALUE); 136 | } else if (PROPNAME == "lastwindowclass") { 137 | g_pWindowManager->statusBar->setLastWindowClass(PROPVALUE); 138 | } else if (PROPNAME == "barfullscreenwindow") { 139 | // deprecated 140 | } 141 | } 142 | } catch(...) { 143 | Debug::log(WARN, "Error in reading Message B!"); 144 | } 145 | } 146 | 147 | void IPCRecieveMessageM(const std::string path) { 148 | // recieve message as main 149 | 150 | if (g_pWindowManager->statusBar) { 151 | Debug::log(ERR, "Tried to read as main from the bar thread?!"); 152 | return; 153 | } 154 | 155 | try { 156 | std::string message = readFromIPCChannel(path); 157 | 158 | const auto EOFPOS = message.find(IPC_END_OF_FILE); 159 | 160 | if (EOFPOS == std::string::npos) 161 | return; 162 | 163 | message = message.substr(0, EOFPOS); 164 | 165 | while (message.find(IPC_MESSAGE_SEPARATOR) != std::string::npos && message.find(IPC_MESSAGE_SEPARATOR) != 0) { 166 | // read until done. 167 | const auto PROP = message.substr(0, message.find(IPC_MESSAGE_SEPARATOR)); 168 | message = message.substr(message.find(IPC_MESSAGE_SEPARATOR) + IPC_MESSAGE_SEPARATOR.length()); 169 | 170 | // Get the name and value 171 | const auto PROPNAME = PROP.substr(0, PROP.find(IPC_MESSAGE_EQUALITY)); 172 | const auto PROPVALUE = PROP.substr(PROP.find(IPC_MESSAGE_EQUALITY) + 1); 173 | 174 | if (PROPNAME == "wid") { 175 | try { 176 | g_pWindowManager->barWindowID = stoi(PROPVALUE); 177 | } catch (...) { 178 | } // Try Catch because stoi can be weird 179 | } 180 | } 181 | } catch (...) { 182 | Debug::log(WARN, "Error in reading Message M!"); 183 | } 184 | } -------------------------------------------------------------------------------- /src/ewmh/ewmh.cpp: -------------------------------------------------------------------------------- 1 | #include "ewmh.hpp" 2 | #include "../windowManager.hpp" 3 | 4 | void EWMH::setupInitEWMH() { 5 | Debug::log(LOG, "EWMH init!"); 6 | 7 | EWMHwindow = xcb_generate_id(g_pWindowManager->DisplayConnection); 8 | 9 | Debug::log(LOG, "Allocated ID " + std::to_string(EWMHwindow) + " for the EWMH window."); 10 | 11 | uint32_t values[1] = {1}; 12 | xcb_create_window(g_pWindowManager->DisplayConnection, XCB_COPY_FROM_PARENT, EWMHwindow, g_pWindowManager->Screen->root, 13 | -1, -1, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, 0, values); 14 | 15 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, EWMHwindow, HYPRATOMS["_NET_SUPPORTING_WM_CHECK"], XCB_ATOM_WINDOW, 32, 1, &EWMHwindow); 16 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, EWMHwindow, HYPRATOMS["_NET_WM_NAME"], HYPRATOMS["UTF8_STRING"], 8, strlen("Hypr"), "Hypr"); 17 | 18 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, g_pWindowManager->Screen->root, HYPRATOMS["_NET_WM_NAME"], HYPRATOMS["UTF8_STRING"], 8, strlen("Hypr"), "Hypr"); 19 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, g_pWindowManager->Screen->root, HYPRATOMS["_NET_SUPPORTING_WM_CHECK"], XCB_ATOM_WINDOW, 32, 1, &EWMHwindow); 20 | 21 | // Atoms EWMH 22 | 23 | xcb_atom_t supportedAtoms[HYPRATOMS.size()]; 24 | int i = 0; 25 | for (auto& a : HYPRATOMS) { 26 | supportedAtoms[i] = a.second; 27 | i++; 28 | } 29 | 30 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, g_pWindowManager->Screen->root, HYPRATOMS["_NET_SUPPORTED"], XCB_ATOM_ATOM, 32, sizeof(supportedAtoms) / sizeof(xcb_atom_t), supportedAtoms); 31 | 32 | // delete workarea 33 | xcb_delete_property(g_pWindowManager->DisplayConnection, g_pWindowManager->Screen->root, HYPRATOMS["_NET_WORKAREA"]); 34 | 35 | Debug::log(LOG, "EWMH init done."); 36 | } 37 | 38 | void EWMH::updateCurrentWindow(xcb_window_t w) { 39 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, g_pWindowManager->Screen->root, HYPRATOMS["_NET_ACTIVE_WINDOW"], XCB_ATOM_WINDOW, 32, 1, &w); 40 | } 41 | 42 | void EWMH::updateClientList() { 43 | std::vector windowsList; 44 | 45 | for (auto& w : g_pWindowManager->windows) 46 | if (w.getDrawable() > 0 && !w.getIsFloating()) 47 | windowsList.push_back(w.getDrawable()); 48 | 49 | // hack 50 | xcb_window_t* ArrWindowList = &windowsList[0]; 51 | 52 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, g_pWindowManager->Screen->root, HYPRATOMS["_NET_CLIENT_LIST"], XCB_ATOM_WINDOW, 53 | 32, windowsList.size(), ArrWindowList); 54 | 55 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, g_pWindowManager->Screen->root, HYPRATOMS["_NET_CLIENT_LIST_STACKING"], XCB_ATOM_WINDOW, 56 | 32, windowsList.size(), ArrWindowList); 57 | } 58 | 59 | void EWMH::refreshAllExtents() { 60 | for (auto& w : g_pWindowManager->windows) 61 | if (w.getDrawable() > 0) 62 | setFrameExtents(w.getDrawable()); 63 | } 64 | 65 | void EWMH::setFrameExtents(xcb_window_t w) { 66 | const auto BORDERSIZE = ConfigManager::getInt("border_size"); 67 | uint32_t extents[4] = {BORDERSIZE,BORDERSIZE,BORDERSIZE,BORDERSIZE}; 68 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, w, HYPRATOMS["_NET_FRAME_EXTENTS"], XCB_ATOM_CARDINAL, 32, 4, &extents); 69 | } 70 | 71 | void EWMH::updateDesktops() { 72 | 73 | int ACTIVEWORKSPACE = -1; 74 | 75 | if (!g_pWindowManager->getMonitorFromCursor()) { 76 | Debug::log(ERR, "Monitor was null! (updateDesktops EWMH) Using LastWindow"); 77 | if (const auto PWINDOW = g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow); PWINDOW) 78 | ACTIVEWORKSPACE = g_pWindowManager->activeWorkspaces[PWINDOW->getWorkspaceID()] - 1; // because xorg counts from zero 79 | else 80 | ACTIVEWORKSPACE = 0; 81 | } else { 82 | ACTIVEWORKSPACE = g_pWindowManager->activeWorkspaces[g_pWindowManager->getMonitorFromCursor()->ID] - 1; 83 | } 84 | 85 | if (DesktopInfo::lastid != ACTIVEWORKSPACE) { 86 | // Update the current workspace 87 | DesktopInfo::lastid = ACTIVEWORKSPACE; 88 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, g_pWindowManager->Screen->root, HYPRATOMS["_NET_CURRENT_DESKTOP"], XCB_ATOM_CARDINAL, 32, 1, &ACTIVEWORKSPACE); 89 | 90 | 91 | // Update all desktops 92 | const auto ALLDESKTOPS = g_pWindowManager->workspaces.size(); 93 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, g_pWindowManager->Screen->root, HYPRATOMS["_NET_NUMBER_OF_DESKTOPS"], XCB_ATOM_CARDINAL, 32, 1, &ALLDESKTOPS); 94 | 95 | // Update desktop names, create a sorted workspaces vec 96 | auto workspacesVec = g_pWindowManager->workspaces; 97 | std::sort(workspacesVec.begin(), workspacesVec.end(), [](CWorkspace& a, CWorkspace& b) { return a.getID() < b.getID(); }); 98 | 99 | int msglen = 0; 100 | for (auto& work : workspacesVec) { 101 | if (work.getID() == SCRATCHPAD_ID) 102 | continue; 103 | msglen += strlen(std::to_string(work.getID()).c_str()) + 1; 104 | } 105 | 106 | char names[msglen]; 107 | int curpos = 0; 108 | for (auto& work : workspacesVec) { 109 | for (int i = 0; i < strlen(std::to_string(work.getID()).c_str()) + 1; ++i) { 110 | if (work.getID() == SCRATCHPAD_ID) 111 | break; 112 | 113 | names[curpos] = std::to_string(work.getID())[i]; 114 | ++curpos; 115 | } 116 | } 117 | 118 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, g_pWindowManager->Screen->root, HYPRATOMS["_NET_DESKTOP_NAMES"], HYPRATOMS["UTF8_STRING"], 8, msglen, names); 119 | 120 | // Also update where the workspaces are so that bars and shit can read which monitor they belong to. 121 | uint32_t workspaceCoords[ALLDESKTOPS * 2]; 122 | 123 | int pos = 0; 124 | for (int i = 0; i < ALLDESKTOPS; ++i) { 125 | workspaceCoords[pos++] = g_pWindowManager->monitors[g_pWindowManager->workspaces[i].getMonitor()].vecPosition.x; 126 | workspaceCoords[pos++] = g_pWindowManager->monitors[g_pWindowManager->workspaces[i].getMonitor()].vecPosition.y; 127 | } 128 | 129 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, g_pWindowManager->Screen->root, HYPRATOMS["_NET_DESKTOP_VIEWPORT"], XCB_ATOM_CARDINAL, 32, pos, &workspaceCoords); 130 | } 131 | } 132 | 133 | void EWMH::updateWindow(xcb_window_t win) { 134 | const auto PWINDOW = g_pWindowManager->getWindowFromDrawable(win); 135 | 136 | if (!PWINDOW || win < 1) 137 | return; 138 | 139 | const auto WORKSPACE = PWINDOW->getWorkspaceID() - 1; // because xorgs counts from 0, part 2 140 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, win, HYPRATOMS["_NET_WM_DESKTOP"], XCB_ATOM_CARDINAL, 32, 1, &WORKSPACE); 141 | 142 | // ICCCM State Normal 143 | if (!PWINDOW->getDock()) { 144 | long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE}; 145 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, win, HYPRATOMS["WM_STATE"], HYPRATOMS["WM_STATE"], 32, 2, data); 146 | 147 | if (PWINDOW->getDrawable() == g_pWindowManager->LastWindow) { 148 | uint32_t dataa[] = {HYPRATOMS["_NET_WM_STATE_FOCUSED"]}; 149 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_APPEND, PWINDOW->getDrawable(), HYPRATOMS["_NET_WM_STATE"], XCB_ATOM_ATOM, 32, 1, dataa); 150 | } else { 151 | removeAtom(PWINDOW->getDrawable(), HYPRATOMS["_NET_WM_STATE"], HYPRATOMS["_NET_WM_STATE_FOCUSED"]); 152 | } 153 | } 154 | } 155 | 156 | void EWMH::checkTransient(xcb_window_t window) { 157 | 158 | const auto PWINDOW = g_pWindowManager->getWindowFromDrawable(window); 159 | 160 | if (!PWINDOW) 161 | return; 162 | 163 | // Check if it's a transient 164 | const auto TRANSIENTCOOKIE = xcb_get_property(g_pWindowManager->DisplayConnection, false, window, 68 /* TRANSIENT_FOR */, XCB_GET_PROPERTY_TYPE_ANY, 0, UINT32_MAX); 165 | const auto TRANSIENTREPLY = xcb_get_property_reply(g_pWindowManager->DisplayConnection, TRANSIENTCOOKIE, NULL); 166 | 167 | if (!TRANSIENTREPLY || xcb_get_property_value_length(TRANSIENTREPLY) == 0) { 168 | Debug::log(WARN, "Transient check failed."); 169 | return; 170 | } 171 | 172 | xcb_window_t transientWindow; 173 | if (!xcb_icccm_get_wm_transient_for_from_reply(&transientWindow, TRANSIENTREPLY)) { 174 | Debug::log(WARN, "Transient reply failed."); 175 | free(TRANSIENTREPLY); 176 | return; 177 | } 178 | 179 | // set the flags 180 | const auto PPARENTWINDOW = g_pWindowManager->getWindowFromDrawable(transientWindow); 181 | 182 | if (!PPARENTWINDOW) { 183 | free(TRANSIENTREPLY); 184 | Debug::log(LOG, "Transient set for a nonexistent window, ignoring."); 185 | return; 186 | } 187 | 188 | PPARENTWINDOW->addTransientChild(window); 189 | 190 | Debug::log(LOG, "Added a transient child to " + std::to_string(transientWindow) + "."); 191 | 192 | free(TRANSIENTREPLY); 193 | } -------------------------------------------------------------------------------- /src/windowManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defines.hpp" 4 | #include "window.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "KeybindManager.hpp" 12 | #include "utilities/Workspace.hpp" 13 | #include "config/ConfigManager.hpp" 14 | #include "utilities/Monitor.hpp" 15 | #include "utilities/Util.hpp" 16 | #include "utilities/AnimationUtil.hpp" 17 | #include "utilities/XCBProps.hpp" 18 | #include "ewmh/ewmh.hpp" 19 | #include "bar/Bar.hpp" 20 | #include "utilities/Tray.hpp" 21 | 22 | #include "ipc/ipc.hpp" 23 | 24 | class CWindowManager { 25 | public: 26 | xcb_connection_t* DisplayConnection = nullptr; 27 | xcb_ewmh_connection_t* EWMHConnection = nullptr; // Bar uses this 28 | xcb_screen_t* Screen = nullptr; 29 | xcb_drawable_t Drawable; 30 | int RandREventBase = -1; 31 | uint32_t Values[3]; 32 | 33 | // holds the objects of all active monitors. 34 | std::vector monitors; 35 | 36 | bool modKeyDown = false; 37 | int mouseKeyDown = 0; 38 | Vector2D mouseLastPos = Vector2D(0, 0); 39 | int64_t actingOnWindowFloating = 0; 40 | 41 | bool scratchpadActive = false; 42 | 43 | uint8_t Depth = 32; 44 | xcb_visualtype_t* VisualType; 45 | xcb_colormap_t Colormap; 46 | 47 | std::deque windows; // windows never left. It has always been hiding amongst us. 48 | std::deque unmappedWindows; 49 | xcb_drawable_t LastWindow = -1; 50 | 51 | // holds the objects representing every open workspace 52 | std::deque workspaces; 53 | // holds the IDs of open workspaces, Monitor ID -> workspace ID 54 | std::deque activeWorkspaces; 55 | int lastActiveWorkspaceID = 1; 56 | int activeWorkspaceID = 1; 57 | 58 | // Not really pipes, but files. Oh well. Used for IPC. 59 | SIPCPipe m_sIPCBarPipeIn = {ISDEBUG ? "/tmp/hypr/hyprbarind" : "/tmp/hypr/hyprbarin", 0}; 60 | SIPCPipe m_sIPCBarPipeOut = {ISDEBUG ? "/tmp/hypr/hyprbaroutd" : "/tmp/hypr/hyprbarout", 0}; 61 | // This will be nullptr on the main thread, and will hold the pointer to the bar object on the bar thread. 62 | CStatusBar* statusBar = nullptr; 63 | Vector2D lastKnownBarPosition = {-1,-1}; 64 | int64_t barWindowID = 0; 65 | GThread* barThread; /* Well right now anything but the bar but lol */ 66 | 67 | std::deque trayclients; 68 | 69 | bool mainThreadBusy = false; 70 | bool animationUtilBusy = false; 71 | 72 | xcb_cursor_t pointerCursor; 73 | xcb_cursor_context_t* pointerContext; 74 | 75 | Vector2D QueuedPointerWarp = {-1, -1}; 76 | 77 | CWindow* getWindowFromDrawable(int64_t); 78 | void addWindowToVectorSafe(CWindow); 79 | void removeWindowFromVectorSafe(int64_t); 80 | 81 | void setupManager(); 82 | bool handleEvent(); 83 | void recieveEvent(); 84 | void refreshDirtyWindows(); 85 | 86 | void setFocusedWindow(xcb_drawable_t); 87 | void refocusWindowOnClosed(); 88 | 89 | void calculateNewWindowParams(CWindow*); 90 | void getICCCMSizeHints(CWindow*); 91 | void fixWindowOnClose(CWindow*); 92 | void closeWindowAllChecks(int64_t); 93 | 94 | void moveActiveWindowTo(char); 95 | void moveActiveFocusTo(char); 96 | void moveActiveWindowToWorkspace(int); 97 | void moveActiveWindowToRelativeWorkspace(int); 98 | void warpCursorTo(Vector2D); 99 | void toggleWindowFullscrenn(const int&); 100 | void recalcAllDocks(); 101 | 102 | void changeWorkspaceByID(int); 103 | void changeToLastWorkspace(); 104 | void setAllWorkspaceWindowsDirtyByID(int); 105 | int getHighestWorkspaceID(); 106 | CWorkspace* getWorkspaceByID(int); 107 | bool isWorkspaceVisible(int workspaceID); 108 | 109 | void setAllWindowsDirty(); 110 | void setAllFloatingWindowsTop(); 111 | void setAWindowTop(xcb_window_t); 112 | 113 | SMonitor* getMonitorFromWindow(CWindow*); 114 | SMonitor* getMonitorFromCursor(); 115 | SMonitor* getMonitorFromCoord(const Vector2D); 116 | 117 | Vector2D getCursorPos(); 118 | 119 | // finds a window that's tiled at cursor. 120 | CWindow* findWindowAtCursor(); 121 | 122 | CWindow* findFirstWindowOnWorkspace(const int&); 123 | CWindow* findPreferredOnScratchpad(); 124 | 125 | bool shouldBeFloatedOnInit(int64_t); 126 | void doPostCreationChecks(CWindow*); 127 | void getICCCMWMProtocols(CWindow*); 128 | 129 | void setupRandrMonitors(); 130 | void createAndOpenAllPipes(); 131 | void setupDepth(); 132 | void setupColormapAndStuff(); 133 | 134 | void updateActiveWindowName(); 135 | void updateBarInfo(); 136 | 137 | int getWindowsOnWorkspace(const int&); 138 | CWindow* getFullscreenWindowByWorkspace(const int&); 139 | 140 | void recalcAllWorkspaces(); 141 | 142 | void moveWindowToUnmapped(int64_t); 143 | void moveWindowToMapped(int64_t); 144 | bool isWindowUnmapped(int64_t); 145 | 146 | void setAllWorkspaceWindowsAboveFullscreen(const int&); 147 | void setAllWorkspaceWindowsUnderFullscreen(const int&); 148 | 149 | void handleClientMessage(xcb_client_message_event_t*); 150 | 151 | bool shouldBeManaged(const int&); 152 | 153 | void changeSplitRatioCurrent(std::string dir); 154 | 155 | void processCursorDeltaOnWindowResizeTiled(CWindow*, const Vector2D&); 156 | 157 | private: 158 | 159 | // Internal WM functions that don't have to be exposed 160 | 161 | void sanityCheckOnWorkspace(int); 162 | CWindow* getNeighborInDir(char dir); 163 | void eatWindow(CWindow* a, CWindow* toEat); 164 | bool canEatWindow(CWindow* a, CWindow* toEat); 165 | bool isNeighbor(CWindow* a, CWindow* b); 166 | void calculateNewTileSetOldTile(CWindow* pWindow); 167 | void calculateNewFloatingWindow(CWindow* pWindow); 168 | void setEffectiveSizePosUsingConfig(CWindow* pWindow); 169 | void cleanupUnusedWorkspaces(); 170 | xcb_visualtype_t* setupColors(const int&); 171 | void updateRootCursor(); 172 | void applyShapeToWindow(CWindow* pWindow); 173 | SMonitor* getMonitorFromWorkspace(const int&); 174 | void recalcEntireWorkspace(const int&); 175 | void fixMasterWorkspaceOnClosed(CWindow* pWindow); 176 | void startWipeAnimOnWorkspace(const int&, const int&); 177 | void focusOnWorkspace(const int&); 178 | void dispatchQueuedWarp(); 179 | CWindow* getMasterForWorkspace(const int&); 180 | void processBarHiding(); 181 | }; 182 | 183 | inline std::unique_ptr g_pWindowManager = std::make_unique(); 184 | 185 | inline std::map HYPRATOMS = { 186 | HYPRATOM("_NET_SUPPORTED"), 187 | HYPRATOM("_NET_SUPPORTING_WM_CHECK"), 188 | HYPRATOM("_NET_WM_NAME"), 189 | HYPRATOM("_NET_WM_VISIBLE_NAME"), 190 | HYPRATOM("_NET_WM_MOVERESIZE"), 191 | HYPRATOM("_NET_WM_STATE_STICKY"), 192 | HYPRATOM("_NET_WM_STATE_FULLSCREEN"), 193 | HYPRATOM("_NET_WM_STATE_DEMANDS_ATTENTION"), 194 | HYPRATOM("_NET_WM_STATE_MODAL"), 195 | HYPRATOM("_NET_WM_STATE_HIDDEN"), 196 | HYPRATOM("_NET_WM_STATE_FOCUSED"), 197 | HYPRATOM("_NET_WM_STATE"), 198 | HYPRATOM("_NET_WM_WINDOW_TYPE"), 199 | HYPRATOM("_NET_WM_WINDOW_TYPE_NORMAL"), 200 | HYPRATOM("_NET_WM_WINDOW_TYPE_DOCK"), 201 | HYPRATOM("_NET_WM_WINDOW_TYPE_DIALOG"), 202 | HYPRATOM("_NET_WM_WINDOW_TYPE_UTILITY"), 203 | HYPRATOM("_NET_WM_WINDOW_TYPE_TOOLBAR"), 204 | HYPRATOM("_NET_WM_WINDOW_TYPE_SPLASH"), 205 | HYPRATOM("_NET_WM_WINDOW_TYPE_MENU"), 206 | HYPRATOM("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"), 207 | HYPRATOM("_NET_WM_WINDOW_TYPE_POPUP_MENU"), 208 | HYPRATOM("_NET_WM_WINDOW_TYPE_TOOLTIP"), 209 | HYPRATOM("_NET_WM_WINDOW_TYPE_NOTIFICATION"), 210 | HYPRATOM("_NET_WM_DESKTOP"), 211 | HYPRATOM("_NET_WM_STRUT_PARTIAL"), 212 | HYPRATOM("_NET_CLIENT_LIST"), 213 | HYPRATOM("_NET_CLIENT_LIST_STACKING"), 214 | HYPRATOM("_NET_CURRENT_DESKTOP"), 215 | HYPRATOM("_NET_NUMBER_OF_DESKTOPS"), 216 | HYPRATOM("_NET_DESKTOP_NAMES"), 217 | HYPRATOM("_NET_DESKTOP_VIEWPORT"), 218 | HYPRATOM("_NET_ACTIVE_WINDOW"), 219 | HYPRATOM("_NET_CLOSE_WINDOW"), 220 | HYPRATOM("_NET_MOVERESIZE_WINDOW"), 221 | HYPRATOM("_NET_WM_USER_TIME"), 222 | HYPRATOM("_NET_STARTUP_ID"), 223 | HYPRATOM("_NET_WORKAREA"), 224 | HYPRATOM("_NET_WM_ICON"), 225 | HYPRATOM("WM_PROTOCOLS"), 226 | HYPRATOM("WM_DELETE_WINDOW"), 227 | HYPRATOM("UTF8_STRING"), 228 | HYPRATOM("WM_STATE"), 229 | HYPRATOM("WM_CLIENT_LEADER"), 230 | HYPRATOM("WM_TAKE_FOCUS"), 231 | HYPRATOM("WM_WINDOW_ROLE"), 232 | HYPRATOM("_NET_REQUEST_FRAME_EXTENTS"), 233 | HYPRATOM("_NET_FRAME_EXTENTS"), 234 | HYPRATOM("_MOTIF_WM_HINTS"), 235 | HYPRATOM("WM_CHANGE_STATE"), 236 | HYPRATOM("_NET_SYSTEM_TRAY_OPCODE"), 237 | HYPRATOM("_NET_SYSTEM_TRAY_COLORS"), 238 | HYPRATOM("_NET_SYSTEM_TRAY_VISUAL"), 239 | HYPRATOM("_NET_SYSTEM_TRAY_ORIENTATION"), 240 | HYPRATOM("_XEMBED_INFO"), 241 | HYPRATOM("MANAGER")}; 242 | -------------------------------------------------------------------------------- /src/KeybindManager.cpp: -------------------------------------------------------------------------------- 1 | #include "KeybindManager.hpp" 2 | #include "utilities/Util.hpp" 3 | #include "events/events.hpp" 4 | #include "windowManager.hpp" 5 | 6 | #include 7 | #include 8 | 9 | Keybind* KeybindManager::findKeybindByKey(int mod, xcb_keysym_t keysym) { 10 | const auto IGNOREMODMASK = KeybindManager::modToMask(ConfigManager::getString("ignore_mod")); 11 | 12 | for(auto& key : KeybindManager::keybinds) { 13 | if (keysym == key.getKeysym() && (mod == key.getMod() || (mod == (key.getMod() | IGNOREMODMASK)))) { 14 | return &key; 15 | } 16 | } 17 | return nullptr; 18 | } 19 | 20 | uint32_t KeybindManager::getKeyCodeFromName(std::string name) { 21 | if (name == "") 22 | return 0; 23 | 24 | if (name.find_first_not_of("1234567890") == std::string::npos) { 25 | const auto KC = std::stoi(name); 26 | 27 | if (KC > 10) // 0-9 are OK for parsing they are on the KB 28 | return std::stoi(name); 29 | } 30 | 31 | 32 | const auto ORIGINALCASENAME = name; 33 | transform(name.begin(), name.end(), name.begin(), ::tolower); 34 | 35 | if (name.length() == 1) { 36 | // key v keysyms can be max 8 places, + space, + 0x 37 | std::string command = "xmodmap -pk | grep -E -o \".{0,11}\\(" + name + "\\)\""; 38 | std::string returnValue = exec(command.c_str()); 39 | 40 | if (returnValue == "") { 41 | Debug::log(ERR, "Key " + name + " returned no results in the xmodmap!"); 42 | return 0; 43 | } 44 | 45 | try { 46 | returnValue = returnValue.substr(returnValue.find("0x") + 2); 47 | returnValue = returnValue.substr(0, returnValue.find_first_of(' ')); 48 | 49 | Debug::log(LOG, "queried for key " + name + " -> response keysym " + returnValue); 50 | 51 | return std::stoi(returnValue, nullptr, 16); // return hex to int 52 | } catch(...) { } 53 | } else { 54 | if (name == "return" || name == "enter") { 55 | return 0xff0d; 56 | } else if (name == "left") { 57 | return 0xff51; 58 | } else if (name == "right") { 59 | return 0xff53; 60 | } else if (name == "up") { 61 | return 0xff52; 62 | } else if (name == "down") { 63 | return 0xff54; 64 | } else if (name == "space") { 65 | return 0x20; 66 | } else { 67 | // unknown key, find in the xmodmap v case insensitive 68 | std::string command = "xmodmap -pk | grep -E -i -o \".{0,11}\\(" + name + "\\)\""; 69 | std::string returnValue = exec(command.c_str()); 70 | 71 | if (returnValue == "") { 72 | Debug::log(ERR, "Key " + name + " returned no results in the xmodmap!"); 73 | return 0; 74 | } 75 | 76 | try { 77 | returnValue = returnValue.substr(returnValue.find("0x") + 2); 78 | returnValue = returnValue.substr(0, returnValue.find_first_of(' ')); 79 | 80 | Debug::log(LOG, "queried for key " + name + " -> response keysym " + returnValue); 81 | 82 | return std::stoi(returnValue, nullptr, 16); // return hex to int 83 | } catch (...) { 84 | } 85 | } 86 | } 87 | 88 | return 0; 89 | } 90 | 91 | unsigned int KeybindManager::modToMask(std::string mod) { 92 | 93 | if (mod.find_first_not_of("1234567890") == std::string::npos && mod != "") 94 | return std::stoi(mod); 95 | 96 | unsigned int sum = 0; 97 | 98 | if (CONTAINS(mod, "SUPER") || CONTAINS(mod, "MOD4")) sum |= XCB_MOD_MASK_4; 99 | if (CONTAINS(mod, "SHIFT")) sum |= XCB_MOD_MASK_SHIFT; 100 | if (CONTAINS(mod, "CTRL")) sum |= XCB_MOD_MASK_CONTROL; 101 | if (CONTAINS(mod, "ALT") || CONTAINS(mod, "MOD1")) sum |= XCB_MOD_MASK_1; 102 | if (CONTAINS(mod, "MOD2")) sum |= XCB_MOD_MASK_2; 103 | if (CONTAINS(mod, "MOD3")) sum |= XCB_MOD_MASK_3; 104 | if (CONTAINS(mod, "MOD5")) sum |= XCB_MOD_MASK_5; 105 | if (CONTAINS(mod, "LOCK")) sum |= XCB_MOD_MASK_LOCK; 106 | 107 | return sum; 108 | } 109 | 110 | xcb_keysym_t KeybindManager::getKeysymFromKeycode(xcb_keycode_t keycode) { 111 | const auto KEYSYMS = xcb_key_symbols_alloc(g_pWindowManager->DisplayConnection); 112 | const auto KEYSYM = (!(KEYSYMS) ? 0 : xcb_key_symbols_get_keysym(KEYSYMS, keycode, 0)); 113 | xcb_key_symbols_free(KEYSYMS); 114 | return KEYSYM; 115 | } 116 | 117 | xcb_keycode_t KeybindManager::getKeycodeFromKeysym(xcb_keysym_t keysym) { 118 | const auto KEYSYMS = xcb_key_symbols_alloc(g_pWindowManager->DisplayConnection); 119 | const auto KEYCODE = (!(KEYSYMS) ? NULL : xcb_key_symbols_get_keycode(KEYSYMS, keysym)); 120 | xcb_key_symbols_free(KEYSYMS); 121 | return KEYCODE ? *KEYCODE : 0; 122 | } 123 | 124 | // Dispatchers 125 | 126 | void KeybindManager::killactive(std::string args) { 127 | // args unused 128 | const auto PLASTWINDOW = g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow); 129 | 130 | if (!PLASTWINDOW) 131 | return; 132 | 133 | if (PLASTWINDOW->getCanKill()) { 134 | // Send a kill message 135 | xcb_client_message_event_t event; 136 | bzero(&event, sizeof(event)); 137 | event.response_type = XCB_CLIENT_MESSAGE; 138 | event.window = PLASTWINDOW->getDrawable(); 139 | event.type = HYPRATOMS["WM_PROTOCOLS"]; 140 | event.format = 32; 141 | event.data.data32[0] = HYPRATOMS["WM_DELETE_WINDOW"]; 142 | event.data.data32[1] = 0; 143 | 144 | xcb_send_event(g_pWindowManager->DisplayConnection, 0, PLASTWINDOW->getDrawable(), XCB_EVENT_MASK_NO_EVENT, (const char*)&event); 145 | 146 | return; // Do not remove it yet. The user might've cancelled the operation or something. We will get 147 | // an unmap event. 148 | } else 149 | xcb_kill_client(g_pWindowManager->DisplayConnection, g_pWindowManager->LastWindow); 150 | 151 | g_pWindowManager->closeWindowAllChecks(g_pWindowManager->LastWindow); 152 | } 153 | 154 | void KeybindManager::call(std::string args) { 155 | 156 | if (fork() == 0) { 157 | //setsid(); 158 | 159 | execl("/bin/sh", "/bin/sh", "-c", args.c_str(), nullptr); 160 | 161 | _exit(0); 162 | } 163 | //wait(NULL); 164 | } 165 | 166 | void KeybindManager::movewindow(std::string arg) { 167 | g_pWindowManager->moveActiveWindowTo(arg[0]); 168 | } 169 | 170 | void KeybindManager::movefocus(std::string arg) { 171 | g_pWindowManager->moveActiveFocusTo(arg[0]); 172 | } 173 | 174 | void KeybindManager::movetorelativeworkspace(std::string arg) { 175 | try { 176 | if (arg == "+") 177 | g_pWindowManager->moveActiveWindowToRelativeWorkspace(1); 178 | else if (arg == "-") 179 | g_pWindowManager->moveActiveWindowToRelativeWorkspace(-1); 180 | } catch (...) { 181 | Debug::log(ERR, "Invalid arg in movetoworkspace, arg: " + arg); 182 | } 183 | 184 | } 185 | 186 | void KeybindManager::movetoworkspace(std::string arg) { 187 | try { 188 | if (arg == "scratchpad") 189 | g_pWindowManager->moveActiveWindowToWorkspace(SCRATCHPAD_ID); 190 | else 191 | g_pWindowManager->moveActiveWindowToWorkspace(stoi(arg)); 192 | } catch (...) { 193 | Debug::log(ERR, "Invalid arg in movetoworkspace, arg: " + arg); 194 | } 195 | 196 | } 197 | 198 | void KeybindManager::changeworkspace(std::string arg) { 199 | int ID = -1; 200 | try { 201 | ID = std::stoi(arg.c_str()); 202 | } catch (...) { ; } 203 | 204 | if (ID != -1) { 205 | Debug::log(LOG, "Changing the current workspace to " + std::to_string(ID)); 206 | g_pWindowManager->changeWorkspaceByID(ID); 207 | } 208 | } 209 | 210 | void KeybindManager::changetolastworkspace(std::string arg) { 211 | Debug::log(LOG, "Changing the current workspace to the last workspace"); 212 | g_pWindowManager->changeToLastWorkspace(); 213 | } 214 | 215 | void KeybindManager::toggleActiveWindowFullscreen(std::string unusedArg) { 216 | g_pWindowManager->toggleWindowFullscrenn(g_pWindowManager->LastWindow); 217 | } 218 | 219 | void KeybindManager::toggleActiveWindowFloating(std::string arg) { 220 | if (const auto PWINDOW = g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow); PWINDOW) { 221 | PWINDOW->setIsFloating(!PWINDOW->getIsFloating()); 222 | PWINDOW->setDirty(true); 223 | PWINDOW->setPinned(false); 224 | 225 | // Fix window as if it's closed if we just made it floating 226 | if (PWINDOW->getIsFloating()) { 227 | g_pWindowManager->fixWindowOnClose(PWINDOW); 228 | g_pWindowManager->calculateNewWindowParams(PWINDOW); 229 | } 230 | else { 231 | // It's remapped again 232 | 233 | // SAVE ALL INFO NOW, THE POINTER WILL BE DEAD 234 | const auto RESTOREACSIZE = PWINDOW->getDefaultSize(); 235 | const auto RESTOREACPOS = PWINDOW->getDefaultPosition(); 236 | const auto RESTOREWINID = PWINDOW->getDrawable(); 237 | const auto RESTORECANKILL = PWINDOW->getCanKill(); 238 | const auto RESTOREPSEUDO = PWINDOW->getPseudoSize(); 239 | const auto RESTOREISPSEUDO = PWINDOW->getIsPseudotiled(); 240 | const auto RESTOREREALS = PWINDOW->getRealSize(); 241 | const auto RESTOREREALP = PWINDOW->getRealPosition(); 242 | const auto RESTOREDRAGT = PWINDOW->getDraggingTiled(); 243 | 244 | g_pWindowManager->removeWindowFromVectorSafe(PWINDOW->getDrawable()); 245 | 246 | CWindow newWindow; 247 | newWindow.setDrawable(RESTOREWINID); 248 | newWindow.setFirstOpen(false); 249 | newWindow.setConstructed(false); 250 | g_pWindowManager->addWindowToVectorSafe(newWindow); 251 | 252 | const auto PNEWWINDOW = Events::remapWindow(RESTOREWINID, true); 253 | 254 | PNEWWINDOW->setDefaultPosition(RESTOREACPOS); 255 | PNEWWINDOW->setDefaultSize(RESTOREACSIZE); 256 | PNEWWINDOW->setCanKill(RESTORECANKILL); 257 | PNEWWINDOW->setPseudoSize(RESTOREPSEUDO); 258 | PNEWWINDOW->setIsPseudotiled(RESTOREISPSEUDO); 259 | PNEWWINDOW->setRealPosition(RESTOREREALP); 260 | PNEWWINDOW->setRealSize(RESTOREREALS); 261 | PNEWWINDOW->setDraggingTiled(RESTOREDRAGT); 262 | } 263 | 264 | // EWMH to let everyone know 265 | EWMH::updateClientList(); 266 | 267 | EWMH::updateWindow(PWINDOW->getDrawable()); 268 | 269 | if (arg != "simple") 270 | g_pWindowManager->setAllFloatingWindowsTop(); 271 | } 272 | } 273 | 274 | void KeybindManager::changeSplitRatio(std::string args) { 275 | g_pWindowManager->changeSplitRatioCurrent(args); 276 | } 277 | 278 | void KeybindManager::togglePseudoActive(std::string args) { 279 | const auto PWINDOW = g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow); 280 | 281 | if (!PWINDOW) 282 | return; 283 | 284 | PWINDOW->setIsPseudotiled(!PWINDOW->getIsPseudotiled()); 285 | 286 | PWINDOW->setDirty(true); 287 | } 288 | 289 | void KeybindManager::toggleScratchpad(std::string args) { 290 | if (g_pWindowManager->getWindowsOnWorkspace(SCRATCHPAD_ID) == 0) 291 | return; 292 | 293 | g_pWindowManager->scratchpadActive = !g_pWindowManager->scratchpadActive; 294 | 295 | g_pWindowManager->setAllWorkspaceWindowsDirtyByID(SCRATCHPAD_ID); 296 | 297 | const auto NEWTOP = g_pWindowManager->findFirstWindowOnWorkspace(SCRATCHPAD_ID); 298 | if (NEWTOP) 299 | g_pWindowManager->setFocusedWindow(NEWTOP->getDrawable()); 300 | } 301 | 302 | void KeybindManager::nextWorkspace(std::string args) { 303 | const auto PMONITOR = g_pWindowManager->getMonitorFromCursor(); 304 | 305 | if (!PMONITOR) 306 | return; 307 | 308 | g_pWindowManager->changeWorkspaceByID(g_pWindowManager->activeWorkspaces[PMONITOR->ID] + 1); 309 | } 310 | 311 | void KeybindManager::lastWorkspace(std::string args) { 312 | const auto PMONITOR = g_pWindowManager->getMonitorFromCursor(); 313 | 314 | if (!PMONITOR) 315 | return; 316 | 317 | g_pWindowManager->changeWorkspaceByID(g_pWindowManager->activeWorkspaces[PMONITOR->ID] < 2 ? 1 : g_pWindowManager->activeWorkspaces[PMONITOR->ID] - 1); 318 | } 319 | 320 | void KeybindManager::pinActive(std::string agrs) { 321 | const auto PWINDOW = g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow); 322 | 323 | if (!PWINDOW) 324 | return; 325 | 326 | if (PWINDOW->getIsFloating()) 327 | PWINDOW->setPinned(!PWINDOW->getPinned()); 328 | } -------------------------------------------------------------------------------- /src/config/ConfigManager.cpp: -------------------------------------------------------------------------------- 1 | #include "ConfigManager.hpp" 2 | #include "../windowManager.hpp" 3 | #include "../KeybindManager.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void ConfigManager::init() { 15 | configValues["border_size"].intValue = 1; 16 | configValues["gaps_in"].intValue = 5; 17 | configValues["gaps_out"].intValue = 20; 18 | configValues["rounding"].intValue = 5; 19 | configValues["main_mod"].strValue = "SUPER"; 20 | configValues["intelligent_transients"].intValue = 1; 21 | configValues["no_unmap_saving"].intValue = 1; 22 | configValues["scratchpad_mon"].intValue = 1; 23 | configValues["ignore_mod"].strValue = "0"; 24 | 25 | configValues["focus_when_hover"].intValue = 1; 26 | 27 | configValues["layout"].intValue = LAYOUT_DWINDLE; 28 | configValues["layout:no_gaps_when_only"].intValue = 0; 29 | 30 | configValues["max_fps"].intValue = 60; 31 | 32 | configValues["bar:monitor"].intValue = 0; 33 | configValues["bar:enabled"].intValue = 1; 34 | configValues["bar:height"].intValue = 15; 35 | configValues["bar:col.bg"].intValue = 0xFF111111; 36 | configValues["bar:col.high"].intValue = 0xFFFF3333; 37 | configValues["bar:col.font_main"].intValue = 0xFFFFFFFF; 38 | configValues["bar:col.font_secondary"].intValue = 0xFF111111; 39 | configValues["bar:font.size"].floatValue = 12; 40 | configValues["bar:font.main"].strValue = "Noto Sans"; 41 | configValues["bar:font.secondary"].strValue = "Noto Sans"; 42 | configValues["bar:mod_pad_in"].intValue = 4; 43 | configValues["bar:no_tray_saving"].intValue = 1; 44 | configValues["bar:force_no_tray"].intValue = 1; 45 | 46 | configValues["status_command"].strValue = "date +%I:%M\\ %p"; // Time // Deprecated 47 | 48 | // Set Colors ARGB 49 | configValues["col.active_border"].intValue = 0x77FF3333; 50 | configValues["col.inactive_border"].intValue = 0x77222222; 51 | 52 | // animations 53 | configValues["animations:speed"].floatValue = 1; 54 | configValues["animations:window_resize_speed"].floatValue = 1; 55 | configValues["animations:enabled"].intValue = 0; 56 | configValues["animations:cheap"].intValue = 1; 57 | configValues["animations:borders"].intValue = 1; 58 | configValues["animations:workspaces"].intValue = 1; 59 | 60 | configValues["autogenerated"].intValue = 0; 61 | 62 | if (!g_pWindowManager->statusBar) { 63 | isFirstLaunch = true; 64 | } 65 | 66 | lastModifyTime = 0; 67 | 68 | tick(); 69 | applyKeybindsToX(); 70 | } 71 | 72 | void configSetValueSafe(const std::string& COMMAND, const std::string& VALUE) { 73 | if (ConfigManager::configValues.find(COMMAND) == ConfigManager::configValues.end()) { 74 | ConfigManager::parseError = "Error setting value <" + VALUE + "> for field <" + COMMAND + ">: No such field."; 75 | return; 76 | } 77 | 78 | auto& CONFIGENTRY = ConfigManager::configValues.at(COMMAND); 79 | if (CONFIGENTRY.intValue != -1) { 80 | try { 81 | if (VALUE.find("0x") == 0) { 82 | // Values with 0x are hex 83 | const auto VALUEWITHOUTHEX = VALUE.substr(2); 84 | CONFIGENTRY.intValue = stol(VALUEWITHOUTHEX, nullptr, 16); 85 | } else 86 | CONFIGENTRY.intValue = stol(VALUE); 87 | } catch (...) { 88 | Debug::log(WARN, "Error reading value of " + COMMAND); 89 | ConfigManager::parseError = "Error setting value <" + VALUE + "> for field <" + COMMAND + ">."; 90 | } 91 | } else if (CONFIGENTRY.floatValue != -1) { 92 | try { 93 | CONFIGENTRY.floatValue = stof(VALUE); 94 | } catch (...) { 95 | Debug::log(WARN, "Error reading value of " + COMMAND); 96 | ConfigManager::parseError = "Error setting value <" + VALUE + "> for field <" + COMMAND + ">."; 97 | } 98 | } else if (CONFIGENTRY.strValue != "") { 99 | try { 100 | CONFIGENTRY.strValue = VALUE; 101 | } catch (...) { 102 | Debug::log(WARN, "Error reading value of " + COMMAND); 103 | ConfigManager::parseError = "Error setting value <" + VALUE + "> for field <" + COMMAND + ">."; 104 | } 105 | } 106 | } 107 | 108 | void handleBind(const std::string& command, const std::string& value) { 109 | 110 | // example: 111 | // bind=SUPER,G,exec,dmenu_run 112 | 113 | auto valueCopy = value; 114 | 115 | const auto MOD = valueCopy.substr(0, valueCopy.find_first_of(",")); 116 | valueCopy = valueCopy.substr(valueCopy.find_first_of(",") + 1); 117 | 118 | const auto KEY = KeybindManager::getKeyCodeFromName(valueCopy.substr(0, valueCopy.find_first_of(","))); 119 | valueCopy = valueCopy.substr(valueCopy.find_first_of(",") + 1); 120 | 121 | const auto HANDLER = valueCopy.substr(0, valueCopy.find_first_of(",")); 122 | valueCopy = valueCopy.substr(valueCopy.find_first_of(",") + 1); 123 | 124 | const auto COMMAND = valueCopy; 125 | 126 | Dispatcher dispatcher = nullptr; 127 | if (HANDLER == "exec") dispatcher = KeybindManager::call; 128 | if (HANDLER == "killactive") dispatcher = KeybindManager::killactive; 129 | if (HANDLER == "fullscreen") dispatcher = KeybindManager::toggleActiveWindowFullscreen; 130 | if (HANDLER == "movewindow") dispatcher = KeybindManager::movewindow; 131 | if (HANDLER == "movefocus") dispatcher = KeybindManager::movefocus; 132 | if (HANDLER == "movetoworkspace") dispatcher = KeybindManager::movetoworkspace; 133 | if (HANDLER == "movetorelativeworkspace") dispatcher = KeybindManager::movetorelativeworkspace; 134 | if (HANDLER == "workspace") dispatcher = KeybindManager::changeworkspace; 135 | if (HANDLER == "lastworkspace") dispatcher = KeybindManager::changetolastworkspace; 136 | if (HANDLER == "togglefloating") dispatcher = KeybindManager::toggleActiveWindowFloating; 137 | if (HANDLER == "splitratio") dispatcher = KeybindManager::changeSplitRatio; 138 | if (HANDLER == "pseudo") dispatcher = KeybindManager::togglePseudoActive; 139 | if (HANDLER == "scratchpad") dispatcher = KeybindManager::toggleScratchpad; 140 | if (HANDLER == "nextworkspace") dispatcher = KeybindManager::nextWorkspace; 141 | if (HANDLER == "lastworkspace") dispatcher = KeybindManager::lastWorkspace; 142 | if (HANDLER == "pin") dispatcher = KeybindManager::pinActive; 143 | 144 | if (dispatcher && KEY != 0) 145 | KeybindManager::keybinds.push_back(Keybind(KeybindManager::modToMask(MOD), KEY, COMMAND, dispatcher)); 146 | } 147 | 148 | void handleRawExec(const std::string& command, const std::string& args) { 149 | // Exec in the background dont wait for it. 150 | RETURNIFBAR; 151 | 152 | if (fork() == 0) { 153 | execl("/bin/sh", "/bin/sh", "-c", args.c_str(), nullptr); 154 | 155 | _exit(0); 156 | } 157 | } 158 | 159 | void handleStatusCommand(const std::string& command, const std::string& args) { 160 | if (g_pWindowManager->statusBar) 161 | g_pWindowManager->statusBar->setStatusCommand(args); 162 | } 163 | 164 | void parseModule(const std::string& COMMANDC, const std::string& VALUE) { 165 | SBarModule* module = new SBarModule(); 166 | g_pWindowManager->statusBar->modules.push_back(module); 167 | 168 | auto valueCopy = VALUE; 169 | 170 | const auto ALIGN = valueCopy.substr(0, valueCopy.find_first_of(",")); 171 | valueCopy = valueCopy.substr(valueCopy.find_first_of(",") + 1); 172 | 173 | if (ALIGN == "pad") { 174 | const auto ALIGNR = valueCopy.substr(0, valueCopy.find_first_of(",")); 175 | valueCopy = valueCopy.substr(valueCopy.find_first_of(",") + 1); 176 | 177 | const auto PADW = valueCopy; 178 | 179 | if (ALIGNR == "left") module->alignment = LEFT; 180 | else if (ALIGNR == "right") module->alignment = RIGHT; 181 | else if (ALIGNR == "center") module->alignment = CENTER; 182 | 183 | try { 184 | module->pad = stol(PADW); 185 | } catch (...) { 186 | Debug::log(ERR, "Module creation pad error: invalid pad"); 187 | ConfigManager::parseError = "Module creation error in pad: invalid pad."; 188 | return; 189 | } 190 | 191 | module->isPad = true; 192 | 193 | module->color = 0; 194 | module->bgcolor = 0; 195 | 196 | return; 197 | } 198 | 199 | const auto ICON = valueCopy.substr(0, valueCopy.find_first_of(",")); 200 | valueCopy = valueCopy.substr(valueCopy.find_first_of(",") + 1); 201 | 202 | const auto COL1 = valueCopy.substr(0, valueCopy.find_first_of(",")); 203 | valueCopy = valueCopy.substr(valueCopy.find_first_of(",") + 1); 204 | 205 | const auto COL2 = valueCopy.substr(0, valueCopy.find_first_of(",")); 206 | valueCopy = valueCopy.substr(valueCopy.find_first_of(",") + 1); 207 | 208 | const auto UPDATE = valueCopy.substr(0, valueCopy.find_first_of(",")); 209 | valueCopy = valueCopy.substr(valueCopy.find_first_of(",") + 1); 210 | 211 | const auto COMMAND = valueCopy; 212 | 213 | if (ALIGN == "left") module->alignment = LEFT; 214 | else if (ALIGN == "right") module->alignment = RIGHT; 215 | else if (ALIGN == "center") module->alignment = CENTER; 216 | 217 | try { 218 | module->color = stol(COL1.substr(2), nullptr, 16); 219 | module->bgcolor = stol(COL2.substr(2), nullptr, 16); 220 | } catch (...) { 221 | Debug::log(ERR, "Module creation color error: invalid color"); 222 | ConfigManager::parseError = "Module creation error in color: invalid color."; 223 | g_pWindowManager->statusBar->modules.pop_back(); 224 | delete module; 225 | return; 226 | } 227 | 228 | try { 229 | module->updateEveryMs = stol(UPDATE); 230 | } catch (...) { 231 | Debug::log(ERR, "Module creation error: invalid update interval"); 232 | ConfigManager::parseError = "Module creation error in interval: invalid interval."; 233 | g_pWindowManager->statusBar->modules.pop_back(); 234 | delete module; 235 | return; 236 | } 237 | 238 | module->icon = ICON; 239 | 240 | module->value = COMMAND; 241 | } 242 | 243 | void handleWindowRule(const std::string& command, const std::string& value) { 244 | const auto RULE = value.substr(0, value.find_first_of(",")); 245 | const auto VALUE = value.substr(value.find_first_of(",") + 1); 246 | 247 | // check rule and value 248 | if (RULE == "" || VALUE == "") { 249 | return; 250 | } 251 | 252 | // verify we support a rule 253 | if (RULE != "float" 254 | && RULE != "tile" 255 | && RULE.find("move") != 0 256 | && RULE.find("size") != 0 257 | && RULE.find("nointerventions") != 0 258 | && RULE.find("pseudo") != 0 259 | && RULE.find("fullscreen") != 0 260 | && RULE.find("workspace") != 0 261 | && RULE.find("monitor") != 0) { 262 | Debug::log(ERR, "Invalid rule found: " + RULE); 263 | ConfigManager::parseError = "Invalid rule found: " + RULE; 264 | return; 265 | } 266 | 267 | ConfigManager::windowRules.push_back({RULE, VALUE}); 268 | } 269 | 270 | void parseLine(std::string& line) { 271 | // first check if its not a comment 272 | const auto COMMENTSTART = line.find_first_of('#'); 273 | if (COMMENTSTART == 0) 274 | return; 275 | 276 | // now, cut the comment off 277 | if (COMMENTSTART != std::string::npos) 278 | line = line.substr(0, COMMENTSTART); 279 | 280 | // remove shit at the beginning 281 | while (line[0] == ' ' || line[0] == '\t') { 282 | line = line.substr(1); 283 | } 284 | 285 | if (line.find(" {") != std::string::npos) { 286 | auto cat = line.substr(0, line.find(" {")); 287 | transform(cat.begin(), cat.end(), cat.begin(), ::tolower); 288 | ConfigManager::currentCategory = cat; 289 | return; 290 | } 291 | 292 | std::size_t closingBrace = line.find("}"); 293 | if (closingBrace != std::string::npos && ConfigManager::currentCategory != "" && 294 | (closingBrace == 0 || line[closingBrace - 1] != '\\')) { 295 | ConfigManager::currentCategory = ""; 296 | return; 297 | } 298 | 299 | // And parse 300 | // check if command 301 | const auto EQUALSPLACE = line.find_first_of('='); 302 | 303 | if (EQUALSPLACE == std::string::npos) 304 | return; 305 | 306 | const auto COMMAND = line.substr(0, EQUALSPLACE); 307 | const auto VALUE = line.substr(EQUALSPLACE + 1); 308 | 309 | if (COMMAND == "bind") { 310 | handleBind(COMMAND, VALUE); 311 | return; 312 | } else if (COMMAND == "exec") { 313 | handleRawExec(COMMAND, VALUE); 314 | return; 315 | } else if (COMMAND == "exec-once") { 316 | if (ConfigManager::isFirstLaunch) 317 | handleRawExec(COMMAND, VALUE); 318 | return; 319 | } else if (COMMAND == "status_command") { 320 | handleStatusCommand(COMMAND, VALUE); 321 | return; 322 | } else if (COMMAND == "windowrule") { 323 | handleWindowRule(COMMAND, VALUE); 324 | return; 325 | } 326 | 327 | if (COMMAND == "module" && ConfigManager::currentCategory == "bar") { 328 | if (g_pWindowManager->statusBar) 329 | parseModule(COMMAND, VALUE); 330 | return; 331 | } 332 | 333 | configSetValueSafe(ConfigManager::currentCategory + (ConfigManager::currentCategory == "" ? "" : ":") + COMMAND, VALUE); 334 | 335 | } 336 | 337 | void ConfigManager::loadConfigLoadVars() { 338 | const auto ORIGBORDERSIZE = configValues["border_size"].intValue; 339 | Debug::log(LOG, "Reloading the config!"); 340 | ConfigManager::parseError = ""; // reset the error 341 | ConfigManager::currentCategory = ""; // reset the category 342 | ConfigManager::windowRules.clear(); // Clear rules 343 | 344 | configValues["autogenerated"].intValue = 0; 345 | 346 | if (loadBar && g_pWindowManager->statusBar) { 347 | // clear modules as we overwrite them 348 | for (auto& m : g_pWindowManager->statusBar->modules) { 349 | g_pWindowManager->statusBar->destroyModule(m); 350 | } 351 | g_pWindowManager->statusBar->modules.clear(); 352 | } 353 | 354 | KeybindManager::keybinds.clear(); 355 | 356 | const char* const ENVHOME = getenv("HOME"); 357 | 358 | const std::string CONFIGPATH = ENVHOME + (ISDEBUG ? (std::string) "/.config/hypr/hyprd.conf" : (std::string) "/.config/hypr/hypr.conf"); 359 | 360 | std::ifstream ifs; 361 | ifs.open(CONFIGPATH.c_str()); 362 | 363 | if (!ifs.good()) { 364 | Debug::log(WARN, "Config reading error. (No file? Attempting to generate, backing up old one if exists)"); 365 | try { 366 | std::filesystem::rename(CONFIGPATH, CONFIGPATH + ".backup"); 367 | } catch (...) { /* Probably doesn't exist */ 368 | } 369 | 370 | std::ofstream ofs; 371 | ofs.open(CONFIGPATH, std::ios::trunc); 372 | 373 | ofs << AUTOCONFIG; 374 | 375 | ofs.close(); 376 | 377 | ifs.open(CONFIGPATH); 378 | 379 | if (!ifs.good()) { 380 | parseError = "Broken config file! (Could not open)"; 381 | return; 382 | } 383 | } 384 | 385 | std::string line = ""; 386 | int linenum = 1; 387 | if (ifs.is_open()) { 388 | while (std::getline(ifs, line)) { 389 | // Read line by line. 390 | try { 391 | parseLine(line); 392 | } catch(...) { 393 | Debug::log(ERR, "Error reading line from config. Line:"); 394 | Debug::log(NONE, line); 395 | 396 | parseError = "Config error at line " + std::to_string(linenum) + ": Line parsing error."; 397 | } 398 | 399 | if (parseError != "" && parseError.find("Config error at line") != 0) { 400 | parseError = "Config error at line " + std::to_string(linenum) + ": " + parseError; 401 | } 402 | 403 | ++linenum; 404 | 405 | } 406 | 407 | ifs.close(); 408 | } 409 | 410 | // recalc all workspaces 411 | g_pWindowManager->recalcAllWorkspaces(); 412 | 413 | if (configValues["autogenerated"].intValue == 1) { 414 | parseError = "Warning: You're using an autogenerated config! (config file: " + CONFIGPATH + " ) SUPER+Enter -> xterm SUPER+T -> Alacritty SUPER+M -> exit Hypr"; 415 | } 416 | 417 | // Reload the bar as well, don't load it before the default is loaded. 418 | if (loadBar && g_pWindowManager->statusBar && (configValues["bar:enabled"].intValue == 1 || parseError != "")) { 419 | g_pWindowManager->statusBar->destroy(); 420 | 421 | // make the bar height visible 422 | if (parseError != "") { 423 | configValues["bar:height"].intValue = 15; 424 | } 425 | 426 | if (configValues["bar:monitor"].intValue > g_pWindowManager->monitors.size()) { 427 | configValues["bar:monitor"].intValue = 0; 428 | Debug::log(ERR, "Incorrect value in MonitorID for the bar. Setting to 0."); 429 | } 430 | 431 | g_pWindowManager->statusBar->setup(configValues["bar:monitor"].intValue); 432 | } else if (g_pWindowManager->statusBar) { 433 | g_pWindowManager->statusBar->destroy(); 434 | } 435 | 436 | // Ensure correct layout 437 | if (configValues["layout"].intValue < 0 || configValues["layout"].intValue > 1) { 438 | Debug::log(ERR, "Invalid layout ID, falling back to 0."); 439 | configValues["layout"].intValue = 0; 440 | } 441 | 442 | loadBar = true; 443 | isFirstLaunch = false; 444 | 445 | if (ORIGBORDERSIZE != configValues["border_size"].intValue) EWMH::refreshAllExtents(); 446 | 447 | // scratchpad mon 448 | if (configValues["scratchpad_mon"].intValue > g_pWindowManager->monitors.size()) { 449 | configValues["scratchpad_mon"].intValue = 0; 450 | Debug::log(ERR, "Invalid scratchpad mon, falling back to 0"); 451 | } 452 | if (const auto PSCRATCH = g_pWindowManager->getWorkspaceByID(SCRATCHPAD_ID); PSCRATCH) 453 | PSCRATCH->setMonitor(configValues["scratchpad_mod"].intValue); 454 | } 455 | 456 | void ConfigManager::applyKeybindsToX() { 457 | if (g_pWindowManager->statusBar) { 458 | Debug::log(LOG, "Not applying the keybinds because status bar not null"); 459 | return; // If we are in the status bar don't do this. 460 | } 461 | 462 | const auto IGNOREMODMASK = KeybindManager::modToMask(configValues["ignore_mod"].strValue); 463 | 464 | Debug::log(LOG, "Applying " + std::to_string(KeybindManager::keybinds.size()) + " keybinds to X."); 465 | 466 | xcb_ungrab_key(g_pWindowManager->DisplayConnection, XCB_GRAB_ANY, g_pWindowManager->Screen->root, XCB_MOD_MASK_ANY); 467 | xcb_ungrab_button(g_pWindowManager->DisplayConnection, XCB_GRAB_ANY, g_pWindowManager->Screen->root, XCB_MOD_MASK_ANY); 468 | 469 | for (auto& keybind : KeybindManager::keybinds) { 470 | xcb_grab_key(g_pWindowManager->DisplayConnection, 1, g_pWindowManager->Screen->root, 471 | keybind.getMod(), KeybindManager::getKeycodeFromKeysym(keybind.getKeysym()), 472 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); 473 | 474 | // ignore mod 475 | xcb_grab_key(g_pWindowManager->DisplayConnection, 1, g_pWindowManager->Screen->root, 476 | keybind.getMod() | IGNOREMODMASK, KeybindManager::getKeycodeFromKeysym(keybind.getKeysym()), 477 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); 478 | } 479 | 480 | xcb_flush(g_pWindowManager->DisplayConnection); 481 | 482 | // MOD + mouse 483 | xcb_grab_button(g_pWindowManager->DisplayConnection, 0, 484 | g_pWindowManager->Screen->root, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE, 485 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, g_pWindowManager->Screen->root, XCB_NONE, 486 | 1, KeybindManager::modToMask(configValues["main_mod"].strValue)); 487 | 488 | xcb_grab_button(g_pWindowManager->DisplayConnection, 0, 489 | g_pWindowManager->Screen->root, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE, 490 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, g_pWindowManager->Screen->root, XCB_NONE, 491 | 3, KeybindManager::modToMask(configValues["main_mod"].strValue)); 492 | 493 | xcb_flush(g_pWindowManager->DisplayConnection); 494 | } 495 | 496 | void ConfigManager::tick() { 497 | const char* const ENVHOME = getenv("HOME"); 498 | 499 | const std::string CONFIGPATH = ENVHOME + (ISDEBUG ? (std::string) "/.config/hypr/hyprd.conf" : (std::string) "/.config/hypr/hypr.conf"); 500 | 501 | struct stat fileStat; 502 | int err = stat(CONFIGPATH.c_str(), &fileStat); 503 | if (err != 0) { 504 | Debug::log(WARN, "Error at ticking config, error " + std::to_string(errno)); 505 | } 506 | 507 | // check if we need to reload cfg 508 | if(fileStat.st_mtime != lastModifyTime) { 509 | lastModifyTime = fileStat.st_mtime; 510 | 511 | ConfigManager::loadConfigLoadVars(); 512 | 513 | // So that X updates our grabbed keys. 514 | ConfigManager::applyKeybindsToX(); 515 | 516 | // so that the WM reloads the windows. 517 | emptyEvent(); 518 | } 519 | } 520 | 521 | int ConfigManager::getInt(std::string v) { 522 | return configValues[v].intValue; 523 | } 524 | 525 | float ConfigManager::getFloat(std::string v) { 526 | return configValues[v].floatValue; 527 | } 528 | 529 | std::string ConfigManager::getString(std::string v) { 530 | return configValues[v].strValue; 531 | } 532 | 533 | std::vector ConfigManager::getMatchingRules(xcb_window_t w) { 534 | const auto PWINDOW = g_pWindowManager->getWindowFromDrawable(w); 535 | 536 | if (!PWINDOW) 537 | return std::vector(); 538 | 539 | std::vector returns; 540 | 541 | for (auto& rule : ConfigManager::windowRules) { 542 | // check if we have a matching rule 543 | if (rule.szValue.find("class:") == 0) { 544 | try { 545 | std::regex classCheck(rule.szValue.substr(strlen("class:"))); 546 | 547 | if (!std::regex_search(PWINDOW->getClassName(), classCheck)) 548 | continue; 549 | } catch (...) { 550 | Debug::log(ERR, "Regex error at " + rule.szValue); 551 | } 552 | } else if (rule.szValue.find("role:") == 0) { 553 | try { 554 | std::regex roleCheck(rule.szValue.substr(strlen("role:"))); 555 | 556 | if (!std::regex_search(PWINDOW->getRoleName(), roleCheck)) 557 | continue; 558 | } catch (...) { 559 | Debug::log(ERR, "Regex error at " + rule.szValue); 560 | } 561 | } else { 562 | continue; 563 | } 564 | 565 | // applies. Read the rule and behave accordingly 566 | Debug::log(LOG, "Window rule " + rule.szRule + "," + rule.szValue + " matched."); 567 | 568 | returns.push_back(rule); 569 | } 570 | 571 | return returns; 572 | } 573 | -------------------------------------------------------------------------------- /src/bar/Bar.cpp: -------------------------------------------------------------------------------- 1 | #include "Bar.hpp" 2 | 3 | #include 4 | #include 5 | #include "../config/ConfigManager.hpp" 6 | #include "../windowManager.hpp" 7 | 8 | bool isParentDead() { 9 | const auto PPID = getppid(); 10 | 11 | return PPID == 1; 12 | } 13 | 14 | void parseEvent() { 15 | while(1) { 16 | g_pWindowManager->recieveEvent(); 17 | } 18 | } 19 | 20 | int64_t barMainThread() { 21 | // Main already created all the pipes 22 | 23 | Debug::log(LOG, "Child says Hello World!"); 24 | 25 | // Well now this is the init 26 | // it's pretty tricky because we only need to init the stuff we need 27 | g_pWindowManager->DisplayConnection = xcb_connect(NULL, &barScreen); 28 | if (const auto RET = xcb_connection_has_error(g_pWindowManager->DisplayConnection); RET != 0) { 29 | Debug::log(CRIT, "Connection Failed! Return: " + std::to_string(RET)); 30 | return RET; 31 | } 32 | 33 | // Screen 34 | g_pWindowManager->Screen = xcb_aux_get_screen(g_pWindowManager->DisplayConnection, barScreen); 35 | 36 | if (!g_pWindowManager->Screen) { 37 | Debug::log(CRIT, "Screen was null!"); 38 | return 1; 39 | } 40 | 41 | Debug::log(LOG, "Bar init Phase 1 done."); 42 | 43 | // Init atoms 44 | // get atoms 45 | for (auto& ATOM : HYPRATOMS) { 46 | xcb_intern_atom_cookie_t cookie = xcb_intern_atom(g_pWindowManager->DisplayConnection, 0, ATOM.first.length(), ATOM.first.c_str()); 47 | xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(g_pWindowManager->DisplayConnection, cookie, NULL); 48 | 49 | if (!reply) { 50 | Debug::log(ERR, "Atom failed: " + ATOM.first); 51 | continue; 52 | } 53 | 54 | ATOM.second = reply->atom; 55 | } 56 | 57 | Debug::log(LOG, "Atoms done."); 58 | 59 | g_pWindowManager->EWMHConnection = (xcb_ewmh_connection_t*)malloc(sizeof(xcb_ewmh_connection_t)); 60 | xcb_ewmh_init_atoms_replies(g_pWindowManager->EWMHConnection, xcb_ewmh_init_atoms(g_pWindowManager->DisplayConnection, g_pWindowManager->EWMHConnection), nullptr); 61 | 62 | Debug::log(LOG, "Bar init EWMH done."); 63 | 64 | // Init randr for monitors. 65 | g_pWindowManager->setupRandrMonitors(); 66 | 67 | // Init depth 68 | g_pWindowManager->setupColormapAndStuff(); 69 | 70 | // Setup our bar 71 | CStatusBar STATUSBAR; 72 | 73 | // Tell everyone we are in the child process. 74 | g_pWindowManager->statusBar = &STATUSBAR; 75 | 76 | Debug::log(LOG, "Bar init Phase 2 done."); 77 | 78 | // Start the parse event thread 79 | std::thread([=]() { 80 | parseEvent(); 81 | }).detach(); 82 | 83 | // Init config manager 84 | ConfigManager::init(); 85 | 86 | if (ConfigManager::getInt("bar:enabled") == 1) { 87 | Debug::log(LOG, "Bar enabled, reload config to launch it."); 88 | ConfigManager::loadConfigLoadVars(); 89 | } 90 | 91 | Debug::log(LOG, "Bar setup finished!"); 92 | 93 | int lazyUpdateCounter = 0; 94 | 95 | // setup the tray so apps send to us 96 | if (!STATUSBAR.getIsDestroyed()) 97 | STATUSBAR.setupTray(); 98 | 99 | while (1) { 100 | 101 | // Don't spam these 102 | if (lazyUpdateCounter > 10) { 103 | ConfigManager::tick(); 104 | 105 | lazyUpdateCounter = 0; 106 | } 107 | 108 | ++lazyUpdateCounter; 109 | 110 | // Recieve the message and send our reply 111 | IPCRecieveMessageB(g_pWindowManager->m_sIPCBarPipeIn.szPipeName); 112 | SIPCMessageBarToMain message; 113 | message.windowID = STATUSBAR.getWindowID(); 114 | IPCSendMessage(g_pWindowManager->m_sIPCBarPipeOut.szPipeName, message); 115 | // 116 | 117 | // draw the bar 118 | STATUSBAR.draw(); 119 | 120 | if (isParentDead()) { 121 | // Just for debugging 122 | SIPCMessageBarToMain message; 123 | message.windowID = 0; 124 | IPCSendMessage(g_pWindowManager->m_sIPCBarPipeOut.szPipeName, message); 125 | 126 | Debug::log(LOG, "Bar parent died!"); 127 | 128 | return 0; // If the parent died, kill the bar too. 129 | } 130 | 131 | 132 | std::this_thread::sleep_for(std::chrono::milliseconds(1000 / ConfigManager::getInt("max_fps"))); 133 | } 134 | 135 | return 0; 136 | } 137 | 138 | void CStatusBar::setupModule(SBarModule* module) { 139 | uint32_t values[2]; 140 | module->bgcontext = xcb_generate_id(g_pWindowManager->DisplayConnection); 141 | values[0] = module->bgcolor; 142 | values[1] = module->bgcolor; 143 | xcb_create_gc(g_pWindowManager->DisplayConnection, module->bgcontext, m_iPixmap, XCB_GC_BACKGROUND | XCB_GC_FOREGROUND, values); 144 | } 145 | 146 | void CStatusBar::destroyModule(SBarModule* module) { 147 | if (module->bgcontext) 148 | xcb_free_gc(g_pWindowManager->DisplayConnection, module->bgcontext); 149 | 150 | // delete it from the heap 151 | delete module; 152 | } 153 | 154 | void CStatusBar::setupTray() { 155 | if (ConfigManager::getInt("bar:force_no_tray") == 1) 156 | return; 157 | 158 | Debug::log(LOG, "Setting up tray!"); 159 | 160 | char atomName[strlen("_NET_SYSTEM_TRAY_S") + 11]; 161 | 162 | snprintf(atomName, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", barScreen); 163 | 164 | // init the atom 165 | const auto TRAYCOOKIE = xcb_intern_atom(g_pWindowManager->DisplayConnection, 0, strlen(atomName), atomName); 166 | 167 | trayWindowID = xcb_generate_id(g_pWindowManager->DisplayConnection); 168 | 169 | uint32_t values[] = {g_pWindowManager->Screen->black_pixel, g_pWindowManager->Screen->black_pixel, 1, g_pWindowManager->Colormap}; 170 | 171 | xcb_create_window(g_pWindowManager->DisplayConnection, g_pWindowManager->Depth, trayWindowID, 172 | g_pWindowManager->Screen->root, -1, -1, 1, 1, 0, 173 | XCB_WINDOW_CLASS_INPUT_OUTPUT, g_pWindowManager->VisualType->visual_id, 174 | XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_COLORMAP, 175 | values); 176 | 177 | xcb_atom_t dockAtom[] = {HYPRATOMS["_NET_WM_WINDOW_TYPE_DOCK"]}; 178 | xcb_ewmh_set_wm_window_type(g_pWindowManager->EWMHConnection, trayWindowID, 1, dockAtom); 179 | 180 | const uint32_t ORIENTATION = 0; // Horizontal 181 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, trayWindowID, 182 | HYPRATOMS["_NET_SYSTEM_TRAY_ORIENTATION"], XCB_ATOM_CARDINAL, 183 | 32, 1, &ORIENTATION); 184 | 185 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, trayWindowID, 186 | HYPRATOMS["_NET_SYSTEM_TRAY_VISUAL"], XCB_ATOM_VISUALID, 187 | 32, 1, &g_pWindowManager->VisualType->visual_id); 188 | 189 | // COLORS 190 | 191 | // Check if the tray module is active 192 | SBarModule* pBarModule = nullptr; 193 | for (auto& mod : modules) { 194 | if (mod->value == "tray") { 195 | pBarModule = mod; 196 | break; 197 | } 198 | } 199 | 200 | if (pBarModule) { 201 | // init colors 202 | const auto R = (uint16_t)(RED(pBarModule->bgcolor) * 255.f); 203 | const auto G = (uint16_t)(GREEN(pBarModule->bgcolor) * 255.f); 204 | const auto B = (uint16_t)(BLUE(pBarModule->bgcolor) * 255.f); 205 | 206 | const unsigned short TRAYCOLORS[] = { 207 | R, G, B, R, G, B, R, G, B, R, G, B // Foreground, Error, Warning, Success 208 | }; 209 | 210 | xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, trayWindowID, 211 | HYPRATOMS["_NET_SYSTEM_TRAY_COLORS"], XCB_ATOM_CARDINAL, 32, 12, TRAYCOLORS); 212 | } 213 | 214 | // 215 | 216 | const auto TRAYREPLY = xcb_intern_atom_reply(g_pWindowManager->DisplayConnection, TRAYCOOKIE, NULL); 217 | 218 | if (!TRAYREPLY) { 219 | Debug::log(ERR, "Tray reply NULL! Aborting tray..."); 220 | free(TRAYREPLY); 221 | return; 222 | } 223 | 224 | // set the owner and check 225 | xcb_set_selection_owner(g_pWindowManager->DisplayConnection, trayWindowID, TRAYREPLY->atom, XCB_CURRENT_TIME); 226 | 227 | const auto SELCOOKIE = xcb_get_selection_owner(g_pWindowManager->DisplayConnection, TRAYREPLY->atom); 228 | const auto SELREPLY = xcb_get_selection_owner_reply(g_pWindowManager->DisplayConnection, SELCOOKIE, NULL); 229 | 230 | if (!SELREPLY) { 231 | Debug::log(ERR, "Selection owner reply NULL! Aborting tray..."); 232 | free(SELREPLY); 233 | free(TRAYREPLY); 234 | return; 235 | } 236 | 237 | if (SELREPLY->owner != trayWindowID) { 238 | Debug::log(ERR, "Couldn't set the Tray owner, maybe a different tray is running??"); 239 | free(SELREPLY); 240 | free(TRAYREPLY); 241 | return; 242 | } 243 | 244 | free(SELREPLY); 245 | free(TRAYREPLY); 246 | 247 | Debug::log(LOG, "Tray setup done, sending message!"); 248 | 249 | uint8_t buf[32] = {NULL}; 250 | xcb_client_message_event_t* event = (xcb_client_message_event_t*)buf; 251 | 252 | event->response_type = XCB_CLIENT_MESSAGE; 253 | event->window = g_pWindowManager->Screen->root; 254 | event->type = HYPRATOMS["MANAGER"]; 255 | event->format = 32; 256 | event->data.data32[0] = 0L; 257 | event->data.data32[1] = TRAYREPLY->atom; 258 | event->data.data32[2] = trayWindowID; 259 | 260 | xcb_send_event(g_pWindowManager->DisplayConnection, 0, g_pWindowManager->Screen->root, 0xFFFFFF, (char*)buf); 261 | 262 | Debug::log(LOG, "Tray message sent!"); 263 | } 264 | 265 | void CStatusBar::fixTrayOnCreate() { 266 | if (ConfigManager::getInt("bar:force_no_tray") == 1) 267 | return; 268 | 269 | if (m_bHasTray && ConfigManager::getInt("bar:no_tray_saving") == 0) { 270 | for (auto& tray : g_pWindowManager->trayclients) { 271 | xcb_reparent_window(g_pWindowManager->DisplayConnection, tray.window, g_pWindowManager->statusBar->trayWindowID, 0, 0); 272 | xcb_map_window(g_pWindowManager->DisplayConnection, tray.window); 273 | tray.hidden = false; 274 | } 275 | } else { 276 | uint32_t values[2]; 277 | 278 | values[0] = 0; 279 | values[1] = 0; 280 | 281 | for (auto& tray : g_pWindowManager->trayclients) { 282 | tray.hidden = true; 283 | values[0] = 0; 284 | values[1] = 0; 285 | xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); 286 | values[0] = 30000; 287 | values[1] = 30000; 288 | xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); 289 | } 290 | } 291 | } 292 | 293 | void CStatusBar::saveTrayOnDestroy() { 294 | if (ConfigManager::getInt("bar:force_no_tray") == 1) 295 | return; 296 | 297 | // TODO: fix this instead of disabling it. 298 | 299 | if (ConfigManager::getInt("bar:no_tray_saving") == 1) 300 | return; 301 | 302 | for (auto& tray : g_pWindowManager->trayclients) { 303 | xcb_reparent_window(g_pWindowManager->DisplayConnection, tray.window, g_pWindowManager->Screen->root, 30000, 30000); 304 | } 305 | } 306 | 307 | void CStatusBar::setup(int MonitorID) { 308 | Debug::log(LOG, "Creating the bar!"); 309 | 310 | if (MonitorID > g_pWindowManager->monitors.size()) { 311 | MonitorID = 0; 312 | Debug::log(ERR, "Incorrect value in MonitorID for the bar. Setting to 0."); 313 | } 314 | 315 | m_bHasTray = false; 316 | for (auto& mod : g_pWindowManager->statusBar->modules) { 317 | if (mod->value == "tray") { 318 | m_bHasTray = true; 319 | break; 320 | } 321 | } 322 | 323 | if (ConfigManager::getInt("bar:force_no_tray") == 1) 324 | m_bHasTray = false; 325 | 326 | const auto MONITOR = g_pWindowManager->monitors[MonitorID]; 327 | 328 | Debug::log(LOG, "Bar monitor found to be " + std::to_string(MONITOR.ID)); 329 | 330 | m_iMonitorID = MonitorID; 331 | m_vecPosition = MONITOR.vecPosition; 332 | m_vecSize = Vector2D(MONITOR.vecSize.x, ConfigManager::getInt("bar:height")); 333 | 334 | uint32_t values[4]; 335 | 336 | // window 337 | m_iWindowID = (xcb_generate_id(g_pWindowManager->DisplayConnection)); 338 | 339 | // send the message IMMEDIATELY so that the main thread has time to update our WID. 340 | SIPCMessageBarToMain message; 341 | message.windowID = m_iWindowID; 342 | IPCSendMessage(g_pWindowManager->m_sIPCBarPipeOut.szPipeName, message); 343 | 344 | values[0] = ConfigManager::getInt("bar:col.bg"); 345 | values[1] = ConfigManager::getInt("bar:col.bg"); 346 | values[2] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE; 347 | values[3] = g_pWindowManager->Colormap; 348 | 349 | xcb_create_window(g_pWindowManager->DisplayConnection, g_pWindowManager->Depth, m_iWindowID, 350 | g_pWindowManager->Screen->root, m_vecPosition.x, m_vecPosition.y, m_vecSize.x, m_vecSize.y, 351 | 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, g_pWindowManager->VisualType->visual_id, 352 | XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP, values); 353 | 354 | // Set the state to dock to avoid some issues 355 | xcb_atom_t dockAtom[] = { HYPRATOMS["_NET_WM_WINDOW_TYPE_DOCK"] }; 356 | xcb_ewmh_set_wm_window_type(g_pWindowManager->EWMHConnection, m_iWindowID, 1, dockAtom); 357 | 358 | // map 359 | xcb_map_window(g_pWindowManager->DisplayConnection, m_iWindowID); 360 | Debug::log(LOG, "Bar mapping!"); 361 | 362 | // Create a pixmap for writing to. 363 | m_iPixmap = xcb_generate_id(g_pWindowManager->DisplayConnection); 364 | xcb_create_pixmap(g_pWindowManager->DisplayConnection, g_pWindowManager->Depth, m_iPixmap, m_iWindowID, m_vecSize.x, m_vecSize.y); 365 | 366 | // setup contexts 367 | 368 | auto contextBG = &m_mContexts["BG"]; 369 | contextBG->GContext = xcb_generate_id(g_pWindowManager->DisplayConnection); 370 | 371 | values[0] = ConfigManager::getInt("bar:col.bg"); 372 | values[1] = ConfigManager::getInt("bar:col.bg"); 373 | xcb_create_gc(g_pWindowManager->DisplayConnection, contextBG->GContext, m_iPixmap, XCB_GC_BACKGROUND | XCB_GC_FOREGROUND, values); 374 | 375 | // 376 | // 377 | 378 | m_pCairoSurface = cairo_xcb_surface_create(g_pWindowManager->DisplayConnection, m_iPixmap, g_pWindowManager->VisualType, 379 | m_vecSize.x, m_vecSize.y); 380 | m_pCairo = cairo_create(m_pCairoSurface); 381 | cairo_surface_destroy(m_pCairoSurface); 382 | 383 | // fix tray 384 | fixTrayOnCreate(); 385 | 386 | Debug::log(LOG, "Bar setup done!"); 387 | 388 | m_bIsDestroyed = false; 389 | } 390 | 391 | void CStatusBar::destroy() { 392 | Debug::log(LOG, "Destroying the bar!"); 393 | 394 | if (m_bIsDestroyed) 395 | return; 396 | 397 | saveTrayOnDestroy(); 398 | 399 | xcb_close_font(g_pWindowManager->DisplayConnection, m_mContexts["HITEXT"].Font); 400 | xcb_unmap_window(g_pWindowManager->DisplayConnection, m_iWindowID); 401 | xcb_destroy_window(g_pWindowManager->DisplayConnection, m_iWindowID); 402 | xcb_destroy_window(g_pWindowManager->DisplayConnection, m_iPixmap); 403 | 404 | xcb_free_gc(g_pWindowManager->DisplayConnection, m_mContexts["BG"].GContext); 405 | xcb_free_gc(g_pWindowManager->DisplayConnection, m_mContexts["MEDBG"].GContext); 406 | xcb_free_gc(g_pWindowManager->DisplayConnection, m_mContexts["TEXT"].GContext); 407 | xcb_free_gc(g_pWindowManager->DisplayConnection, m_mContexts["HITEXT"].GContext); 408 | 409 | // Free cairo 410 | cairo_destroy(m_pCairo); 411 | m_pCairo = nullptr; 412 | 413 | m_bIsDestroyed = true; 414 | } 415 | 416 | int CStatusBar::getTextWidth(std::string text, std::string font, double size) { 417 | cairo_select_font_face(m_pCairo, font.c_str(), CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 418 | cairo_set_font_size(m_pCairo, size); 419 | 420 | cairo_text_extents_t textextents; 421 | cairo_text_extents(m_pCairo, text.c_str(), &textextents); 422 | 423 | return textextents.x_advance; 424 | } 425 | 426 | void CStatusBar::drawText(Vector2D pos, std::string text, uint32_t color, std::string font, double size) { 427 | cairo_select_font_face(m_pCairo, font.c_str(), CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 428 | cairo_set_font_size(m_pCairo, size); 429 | cairo_set_source_rgba(m_pCairo, RED(color), GREEN(color), BLUE(color), ALPHA(color)); 430 | cairo_move_to(m_pCairo, pos.x, pos.y); 431 | cairo_show_text(m_pCairo, text.c_str()); 432 | } 433 | 434 | void CStatusBar::drawCairoRectangle(Vector2D pos, Vector2D size, uint32_t color) { 435 | cairo_set_source_rgba(m_pCairo, RED(color), GREEN(color), BLUE(color), ALPHA(color)); 436 | cairo_rectangle(m_pCairo, pos.x, pos.y, size.x, size.y); 437 | cairo_fill(m_pCairo); 438 | } 439 | 440 | int CStatusBar::getTextHalfY() { 441 | return m_vecSize.y - (m_vecSize.y - 9) / 2.f; 442 | } 443 | 444 | void CStatusBar::drawErrorScreen() { 445 | if (ConfigManager::getInt("autogenerated") == 1) 446 | drawCairoRectangle(Vector2D(0, 0), m_vecSize, 0xffffff33); 447 | else 448 | drawCairoRectangle(Vector2D(0, 0), m_vecSize, 0xFFaa1111); 449 | 450 | drawText(Vector2D(1, getTextHalfY()), ConfigManager::parseError, 0xff000000, 451 | ConfigManager::getString("bar:font.main"), ConfigManager::getFloat("bar:font.size")); 452 | 453 | // do all the drawing cuz we return later 454 | cairo_surface_flush(m_pCairoSurface); 455 | 456 | xcb_copy_area(g_pWindowManager->DisplayConnection, m_iPixmap, m_iWindowID, m_mContexts["BG"].GContext, 457 | 0, 0, 0, 0, m_vecSize.x, m_vecSize.y); 458 | 459 | xcb_flush(g_pWindowManager->DisplayConnection); 460 | } 461 | 462 | void CStatusBar::draw() { 463 | 464 | if (m_bIsDestroyed) 465 | return; 466 | 467 | if (!m_pCairo) { 468 | Debug::log(ERR, "Cairo is null but attempted to draw!"); 469 | return; 470 | } 471 | 472 | // Clear the entire pixmap 473 | cairo_save(m_pCairo); 474 | cairo_set_operator(m_pCairo, CAIRO_OPERATOR_CLEAR); 475 | cairo_paint(m_pCairo); 476 | cairo_restore(m_pCairo); 477 | // 478 | 479 | if (ConfigManager::parseError != "") { 480 | // draw a special error screen 481 | drawErrorScreen(); 482 | return; 483 | } 484 | 485 | drawCairoRectangle(Vector2D(0, 0), m_vecSize, ConfigManager::getInt("bar:col.bg")); 486 | 487 | // 488 | // 489 | // DRAW ALL MODULES 490 | 491 | int offLeft = 0; 492 | int offRight = 0; 493 | 494 | for (auto& module : modules) { 495 | 496 | if (!module->bgcontext && !module->isPad) 497 | setupModule(module); 498 | 499 | if (module->value == "workspaces") { 500 | offLeft += drawWorkspacesModule(module, offLeft); 501 | } else { 502 | if (module->alignment == LEFT) { 503 | offLeft += drawModule(module, offLeft); 504 | } else if (module->alignment == RIGHT) { 505 | offRight += drawModule(module, offRight); 506 | } else { 507 | drawModule(module, 0); 508 | } 509 | } 510 | } 511 | 512 | // 513 | // 514 | // 515 | 516 | // fix the fucking tray 517 | if (!m_bHasTray) { 518 | uint32_t values[2]; 519 | for (auto& tray : g_pWindowManager->trayclients) { 520 | tray.hidden = true; 521 | values[0] = 30000; 522 | values[1] = 30000; 523 | xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); 524 | } 525 | } 526 | 527 | 528 | cairo_surface_flush(m_pCairoSurface); 529 | 530 | xcb_copy_area(g_pWindowManager->DisplayConnection, m_iPixmap, m_iWindowID, m_mContexts["BG"].GContext, 531 | 0, 0, 0, 0, m_vecSize.x, m_vecSize.y); 532 | 533 | xcb_flush(g_pWindowManager->DisplayConnection); 534 | } 535 | 536 | // Returns the width 537 | int CStatusBar::drawWorkspacesModule(SBarModule* mod, int off) { 538 | // Draw workspaces 539 | int drawnWorkspaces = 0; 540 | for (long unsigned int i = 0; i < openWorkspaces.size(); ++i) { 541 | const auto WORKSPACE = openWorkspaces[i]; 542 | 543 | // The LastWindow may be on a different one. This is where the mouse is. 544 | const auto MOUSEWORKSPACEID = m_iCurrentWorkspace; 545 | 546 | if (!WORKSPACE) 547 | continue; 548 | 549 | std::string workspaceName = std::to_string(openWorkspaces[i]); 550 | 551 | drawCairoRectangle(Vector2D(off + m_vecSize.y * drawnWorkspaces, 0), Vector2D(m_vecSize.y, m_vecSize.y), WORKSPACE == MOUSEWORKSPACEID ? ConfigManager::getInt("bar:col.high") : ConfigManager::getInt("bar:col.bg")); 552 | 553 | drawText(Vector2D(off + m_vecSize.y * drawnWorkspaces + m_vecSize.y / 2.f - getTextWidth(workspaceName, ConfigManager::getString("bar:font.main"), ConfigManager::getFloat("bar:font.size")) / 2.f, getTextHalfY()), 554 | workspaceName, WORKSPACE == MOUSEWORKSPACEID ? ConfigManager::getInt("bar:col.font_secondary") : ConfigManager::getInt("bar:col.font_main"), ConfigManager::getString("bar:font.main"), ConfigManager::getFloat("bar:font.size")); 555 | 556 | drawnWorkspaces++; 557 | } 558 | 559 | return drawnWorkspaces * m_vecSize.y; 560 | } 561 | 562 | int CStatusBar::drawTrayModule(SBarModule* mod, int off) { 563 | if (ConfigManager::getInt("bar:force_no_tray") == 1) 564 | return 0; 565 | 566 | const auto PAD = 2; 567 | 568 | const auto ELEMENTWIDTH = (m_vecSize.y - 2 < 1 ? 1 : m_vecSize.y - 2); 569 | 570 | const auto MODULEWIDTH = g_pWindowManager->trayclients.size() * (ELEMENTWIDTH + PAD); 571 | 572 | Vector2D position; 573 | switch (mod->alignment) { 574 | case LEFT: 575 | position = Vector2D(off, 0); 576 | break; 577 | case RIGHT: 578 | position = Vector2D(m_vecSize.x - off - MODULEWIDTH, 0); 579 | break; 580 | case CENTER: 581 | position = Vector2D(m_vecSize.x / 2.f - (MODULEWIDTH) / 2.f, 0); 582 | break; 583 | } 584 | 585 | // draw tray 586 | 587 | if (MODULEWIDTH < 1) 588 | return 0; 589 | 590 | drawCairoRectangle(position, Vector2D(MODULEWIDTH, m_vecSize.y), mod->bgcolor); 591 | 592 | int i = 0; 593 | for (auto& tray : g_pWindowManager->trayclients) { 594 | uint32_t values[] = {(int)(position.x + (i * (ELEMENTWIDTH + PAD)) + PAD / 2.f), (int)position.y + 1, (int)XCB_STACK_MODE_ABOVE}; 595 | 596 | if (tray.hidden) { 597 | values[0] = -999; 598 | values[1] = -999; 599 | xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, 600 | XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_STACK_MODE, values); 601 | continue; 602 | } 603 | 604 | xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, 605 | XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_STACK_MODE, values); 606 | 607 | 608 | // fix the size 609 | values[0] = ELEMENTWIDTH; 610 | values[1] = values[0]; 611 | 612 | xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, 613 | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); 614 | 615 | ++i; 616 | } 617 | 618 | return MODULEWIDTH; 619 | } 620 | 621 | int CStatusBar::drawModule(SBarModule* mod, int off) { 622 | 623 | if (mod->isPad) 624 | return mod->pad; 625 | 626 | if (mod->value == "tray") 627 | return drawTrayModule(mod, off); 628 | 629 | const int PAD = ConfigManager::getInt("bar:mod_pad_in"); 630 | 631 | // check if we need to update 632 | if (std::chrono::duration_cast(std::chrono::system_clock::now() - mod->updateLast).count() > mod->updateEveryMs) { 633 | // This is done asynchronously to prevent lag on especially slower PCs 634 | // but ngl it also did hang on more powerful ones 635 | std::thread([=](){ 636 | const auto RETVAL = BarCommands::parseCommand(mod->value); 637 | mod->accessValueCalculated(true, RETVAL); 638 | mod->updateLast = std::chrono::system_clock::now(); 639 | }).detach(); 640 | } 641 | 642 | // We have the value, draw the module! 643 | 644 | const auto MODULEWIDTH = getTextWidth(mod->valueCalculated, ConfigManager::getString("bar:font.main"), ConfigManager::getFloat("bar:font.size")) + PAD; 645 | const auto ICONWIDTH = getTextWidth(mod->icon, ConfigManager::getString("bar:font.secondary"), ConfigManager::getFloat("bar:font.size")); 646 | 647 | if (!MODULEWIDTH || mod->accessValueCalculated(false) == "") 648 | return 0; // empty module 649 | 650 | Vector2D position; 651 | switch (mod->alignment) { 652 | case LEFT: 653 | position = Vector2D(off, 0); 654 | break; 655 | case RIGHT: 656 | position = Vector2D(m_vecSize.x - off - MODULEWIDTH - ICONWIDTH, 0); 657 | break; 658 | case CENTER: 659 | position = Vector2D(m_vecSize.x / 2.f - (MODULEWIDTH + ICONWIDTH) / 2.f, 0); 660 | break; 661 | } 662 | 663 | drawCairoRectangle(position, Vector2D(MODULEWIDTH + ICONWIDTH, m_vecSize.y), mod->bgcolor); 664 | 665 | drawText(position + Vector2D(PAD / 2, getTextHalfY()), mod->icon, mod->color, 666 | ConfigManager::getString("bar:font.secondary"), ConfigManager::getFloat("bar:font.size")); 667 | drawText(position + Vector2D(PAD / 2 + ICONWIDTH, getTextHalfY()), mod->accessValueCalculated(false), mod->color, 668 | ConfigManager::getString("bar:font.main"), ConfigManager::getFloat("bar:font.size")); 669 | 670 | return MODULEWIDTH + ICONWIDTH; 671 | } 672 | 673 | void CStatusBar::ensureTrayClientDead(xcb_window_t window) { 674 | if (ConfigManager::getInt("bar:force_no_tray") == 1) 675 | return; 676 | 677 | auto temp = g_pWindowManager->trayclients; 678 | 679 | g_pWindowManager->trayclients.clear(); 680 | 681 | for (auto& trayitem : temp) { 682 | if (trayitem.window != window) 683 | g_pWindowManager->trayclients.push_back(trayitem); 684 | } 685 | 686 | Debug::log(LOG, "Ensured client dead (Bar, Tray)"); 687 | } 688 | 689 | void CStatusBar::ensureTrayClientHidden(xcb_window_t window, bool hide) { 690 | if (ConfigManager::getInt("bar:force_no_tray") == 1) 691 | return; 692 | 693 | for (auto& trayitem : g_pWindowManager->trayclients) { 694 | if (trayitem.window == window) 695 | trayitem.hidden = hide; 696 | } 697 | } -------------------------------------------------------------------------------- /src/events/events.cpp: -------------------------------------------------------------------------------- 1 | #include "events.hpp" 2 | 3 | gpointer handle(gpointer data) { 4 | int lazyUpdateCounter = 0; 5 | 6 | while (1) { 7 | // update animations. They should be thread-safe. 8 | AnimationUtil::move(); 9 | // 10 | 11 | // wait for the main thread to be idle 12 | while (g_pWindowManager->mainThreadBusy) { 13 | std::this_thread::sleep_for(std::chrono::microseconds(1)); 14 | } 15 | 16 | // set state to let the main thread know to wait. 17 | g_pWindowManager->animationUtilBusy = true; 18 | 19 | // Don't spam these 20 | if (lazyUpdateCounter > 10){ 21 | // Update the active window name 22 | g_pWindowManager->updateActiveWindowName(); 23 | 24 | // Update the bar 25 | g_pWindowManager->updateBarInfo(); 26 | 27 | // check config 28 | ConfigManager::tick(); 29 | 30 | lazyUpdateCounter = 0; 31 | } 32 | 33 | ++lazyUpdateCounter; 34 | 35 | // restore anim state 36 | g_pWindowManager->animationUtilBusy = false; 37 | 38 | std::this_thread::sleep_for(std::chrono::milliseconds(1000 / ConfigManager::getInt("max_fps"))); 39 | } 40 | } 41 | 42 | void Events::setThread() { 43 | 44 | // Start a GTK thread so that Cairo does not complain. 45 | 46 | g_pWindowManager->barThread = g_thread_new("HyprST", handle, nullptr); 47 | 48 | if (!g_pWindowManager->barThread) { 49 | Debug::log(ERR, "Gthread failed!"); 50 | return; 51 | } 52 | } 53 | 54 | void Events::eventEnter(xcb_generic_event_t* event) { 55 | const auto E = reinterpret_cast(event); 56 | 57 | RETURNIFBAR; 58 | 59 | if (E->mode != XCB_NOTIFY_MODE_NORMAL) 60 | return; 61 | 62 | if (E->detail == XCB_NOTIFY_DETAIL_INFERIOR) 63 | return; 64 | 65 | const auto PENTERWINDOW = g_pWindowManager->getWindowFromDrawable(E->event); 66 | 67 | if (!PENTERWINDOW){ 68 | 69 | // we entered an unknown window to us. Let's manage it. 70 | Debug::log(LOG, "Entered an unmanaged window. Trying to manage it!"); 71 | 72 | CWindow newEnteredWindow; 73 | newEnteredWindow.setDrawable(E->event); 74 | g_pWindowManager->addWindowToVectorSafe(newEnteredWindow); 75 | 76 | CWindow* pNewWindow; 77 | if (g_pWindowManager->shouldBeFloatedOnInit(E->event)) { 78 | Debug::log(LOG, "Window SHOULD be floating on start."); 79 | pNewWindow = remapFloatingWindow(E->event); 80 | } else { 81 | Debug::log(LOG, "Window should NOT be floating on start."); 82 | pNewWindow = remapWindow(E->event); 83 | } 84 | 85 | if (!pNewWindow) { // oh well. we tried. 86 | g_pWindowManager->removeWindowFromVectorSafe(E->event); 87 | Debug::log(LOG, "Tried to manage, but failed!"); 88 | } 89 | 90 | return; 91 | } 92 | 93 | // Only when focus_when_hover OR floating OR last window floating 94 | if (ConfigManager::getInt("focus_when_hover") == 1 95 | || PENTERWINDOW->getIsFloating() 96 | || (g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow) && g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow)->getIsFloating())) 97 | g_pWindowManager->setFocusedWindow(E->event); 98 | 99 | PENTERWINDOW->setDirty(true); 100 | 101 | if (PENTERWINDOW->getIsSleeping()) { 102 | // Wake it up, fixes some weird shenaningans 103 | wakeUpEvent(E->event); 104 | PENTERWINDOW->setIsSleeping(false); 105 | } 106 | } 107 | 108 | void Events::eventLeave(xcb_generic_event_t* event) { 109 | const auto E = reinterpret_cast(event); 110 | 111 | RETURNIFBAR; 112 | 113 | const auto PENTERWINDOW = g_pWindowManager->getWindowFromDrawable(E->event); 114 | 115 | if (!PENTERWINDOW) 116 | return; 117 | 118 | if (PENTERWINDOW->getIsSleeping()) { 119 | // Wake it up, fixes some weird shenaningans 120 | wakeUpEvent(E->event); 121 | PENTERWINDOW->setIsSleeping(false); 122 | } 123 | } 124 | 125 | void Events::eventDestroy(xcb_generic_event_t* event) { 126 | const auto E = reinterpret_cast(event); 127 | 128 | // let bar check if it wasnt a tray item 129 | if (g_pWindowManager->statusBar) 130 | g_pWindowManager->statusBar->ensureTrayClientDead(E->window); 131 | 132 | RETURNIFBAR; 133 | 134 | Debug::log(LOG, "Destroy called on " + std::to_string(E->window)); 135 | 136 | g_pWindowManager->closeWindowAllChecks(E->window); 137 | 138 | // refocus on new window 139 | g_pWindowManager->refocusWindowOnClosed(); 140 | 141 | // EWMH 142 | EWMH::updateClientList(); 143 | } 144 | 145 | void Events::eventUnmapWindow(xcb_generic_event_t* event) { 146 | const auto E = reinterpret_cast(event); 147 | 148 | // let bar check if it wasnt a tray item 149 | if (g_pWindowManager->statusBar) 150 | g_pWindowManager->statusBar->ensureTrayClientHidden(E->window, true); 151 | 152 | RETURNIFBAR; 153 | 154 | const auto PCLOSEDWINDOW = g_pWindowManager->getWindowFromDrawable(E->window); 155 | 156 | if (!PCLOSEDWINDOW) { 157 | Debug::log(LOG, "Unmap called on an invalid window: " + std::to_string(E->window)); 158 | return; // bullshit window? 159 | } 160 | 161 | Debug::log(LOG, "Unmap called on " + std::to_string(E->window) + " -> " + PCLOSEDWINDOW->getName()); 162 | 163 | if (!PCLOSEDWINDOW->getDock()) 164 | g_pWindowManager->closeWindowAllChecks(E->window); 165 | 166 | // refocus on new window 167 | g_pWindowManager->refocusWindowOnClosed(); 168 | 169 | // EWMH 170 | EWMH::updateClientList(); 171 | } 172 | 173 | CWindow* Events::remapFloatingWindow(int windowID, int forcemonitor) { 174 | // The array is not realloc'd in this method we can make this const 175 | const auto PWINDOWINARR = g_pWindowManager->getWindowFromDrawable(windowID); 176 | 177 | if (!PWINDOWINARR) { 178 | Debug::log(ERR, "remapFloatingWindow called with an invalid window!"); 179 | return nullptr; 180 | } 181 | 182 | PWINDOWINARR->setIsFloating(true); 183 | PWINDOWINARR->setDirty(true); 184 | 185 | auto PMONITOR = g_pWindowManager->getMonitorFromCursor(); 186 | if (!PMONITOR) { 187 | Debug::log(ERR, "Monitor was null! (remapWindow) Using 0."); 188 | PMONITOR = &g_pWindowManager->monitors[0]; 189 | 190 | if (g_pWindowManager->monitors.size() == 0) { 191 | Debug::log(ERR, "Not continuing. Monitors size 0."); 192 | return nullptr; 193 | } 194 | 195 | } 196 | 197 | // Check the monitor rule 198 | for (auto& rule : ConfigManager::getMatchingRules(windowID)) { 199 | if (!PWINDOWINARR->getFirstOpen()) 200 | break; 201 | 202 | if (rule.szRule.find("monitor") == 0) { 203 | try { 204 | const auto MONITOR = stoi(rule.szRule.substr(rule.szRule.find(" ") + 1)); 205 | 206 | Debug::log(LOG, "Rule monitor, applying to window " + std::to_string(windowID)); 207 | 208 | if (MONITOR > g_pWindowManager->monitors.size() || MONITOR < 0) 209 | forcemonitor = -1; 210 | else 211 | forcemonitor = MONITOR; 212 | } catch(...) { 213 | Debug::log(LOG, "Rule monitor failed, rule: " + rule.szRule + "=" + rule.szValue); 214 | } 215 | } 216 | 217 | if (rule.szRule.find("pseudo") == 0) { 218 | PWINDOWINARR->setIsPseudotiled(true); 219 | } 220 | 221 | if (rule.szRule.find("fullscreen") == 0) { 222 | PWINDOWINARR->setFullscreen(true); 223 | } 224 | 225 | if (rule.szRule.find("workspace") == 0) { 226 | try { 227 | const auto WORKSPACE = stoi(rule.szRule.substr(rule.szRule.find(" ") + 1)); 228 | 229 | Debug::log(LOG, "Rule workspace, applying to window " + std::to_string(windowID)); 230 | 231 | g_pWindowManager->changeWorkspaceByID(WORKSPACE); 232 | forcemonitor = g_pWindowManager->getWorkspaceByID(WORKSPACE)->getMonitor(); 233 | } catch (...) { 234 | Debug::log(LOG, "Rule workspace failed, rule: " + rule.szRule + "=" + rule.szValue); 235 | } 236 | } 237 | } 238 | 239 | const auto CURRENTSCREEN = forcemonitor != -1 ? forcemonitor : PMONITOR->ID; 240 | PWINDOWINARR->setWorkspaceID(g_pWindowManager->activeWorkspaces[CURRENTSCREEN]); 241 | PWINDOWINARR->setMonitor(CURRENTSCREEN); 242 | 243 | // Window name 244 | const auto WINNAME = getWindowName(windowID); 245 | Debug::log(LOG, "New window got name: " + WINNAME); 246 | PWINDOWINARR->setName(WINNAME); 247 | 248 | const auto WINCLASSNAME = getClassName(windowID); 249 | Debug::log(LOG, "New window got class: " + WINCLASSNAME.second); 250 | PWINDOWINARR->setClassName(WINCLASSNAME.second); 251 | 252 | // For all floating windows, get their default size 253 | const auto GEOMETRYCOOKIE = xcb_get_geometry(g_pWindowManager->DisplayConnection, windowID); 254 | const auto GEOMETRY = xcb_get_geometry_reply(g_pWindowManager->DisplayConnection, GEOMETRYCOOKIE, 0); 255 | 256 | if (GEOMETRY) { 257 | PWINDOWINARR->setDefaultPosition(g_pWindowManager->monitors[CURRENTSCREEN].vecPosition); 258 | PWINDOWINARR->setDefaultSize(Vector2D(GEOMETRY->width, GEOMETRY->height)); 259 | } else { 260 | Debug::log(ERR, "Geometry failed in remap."); 261 | 262 | PWINDOWINARR->setDefaultPosition(g_pWindowManager->monitors[CURRENTSCREEN].vecPosition); 263 | PWINDOWINARR->setDefaultSize(Vector2D(g_pWindowManager->Screen->width_in_pixels / 2.f, g_pWindowManager->Screen->height_in_pixels / 2.f)); 264 | } 265 | 266 | if (PWINDOWINARR->getDefaultSize().x < 40 || PWINDOWINARR->getDefaultSize().y < 40) { 267 | // min size 268 | PWINDOWINARR->setDefaultSize(Vector2D(std::clamp(PWINDOWINARR->getDefaultSize().x, (double)40, (double)99999), 269 | std::clamp(PWINDOWINARR->getDefaultSize().y, (double)40, (double)99999))); 270 | } 271 | 272 | if (nextWindowCentered) { 273 | PWINDOWINARR->setDefaultPosition(g_pWindowManager->monitors[CURRENTSCREEN].vecPosition + g_pWindowManager->monitors[CURRENTSCREEN].vecSize / 2.f - PWINDOWINARR->getDefaultSize() / 2.f); 274 | } 275 | 276 | // 277 | // Dock Checks 278 | // 279 | const auto wm_type_cookie = xcb_get_property(g_pWindowManager->DisplayConnection, false, windowID, HYPRATOMS["_NET_WM_WINDOW_TYPE"], XCB_GET_PROPERTY_TYPE_ANY, 0, (4294967295U)); 280 | const auto wm_type_cookiereply = xcb_get_property_reply(g_pWindowManager->DisplayConnection, wm_type_cookie, NULL); 281 | xcb_atom_t TYPEATOM = NULL; 282 | if (wm_type_cookiereply == NULL || xcb_get_property_value_length(wm_type_cookiereply) < 1) { 283 | Debug::log(LOG, "No preferred type found. (RemapFloatingWindow)"); 284 | } else { 285 | const auto ATOMS = (xcb_atom_t*)xcb_get_property_value(wm_type_cookiereply); 286 | if (!ATOMS) { 287 | Debug::log(ERR, "Atoms not found in preferred type!"); 288 | } else { 289 | if (xcbContainsAtom(wm_type_cookiereply, HYPRATOMS["_NET_WM_WINDOW_TYPE_DOCK"])) { 290 | // set to floating and set the immovable and nointerventions flag 291 | PWINDOWINARR->setImmovable(true); 292 | PWINDOWINARR->setNoInterventions(true); 293 | 294 | PWINDOWINARR->setDefaultPosition(Vector2D(GEOMETRY->x, GEOMETRY->y)); 295 | PWINDOWINARR->setDefaultSize(Vector2D(GEOMETRY->width, GEOMETRY->height)); 296 | 297 | PWINDOWINARR->setDockAlign(DOCK_TOP); 298 | 299 | // Check reserved 300 | const auto STRUTREPLY = xcb_get_property_reply(g_pWindowManager->DisplayConnection, xcb_get_property(g_pWindowManager->DisplayConnection, false, windowID, HYPRATOMS["_NET_WM_STRUT_PARTIAL"], XCB_GET_PROPERTY_TYPE_ANY, 0, (4294967295U)), NULL); 301 | 302 | if (!STRUTREPLY || xcb_get_property_value_length(STRUTREPLY) == 0) { 303 | Debug::log(ERR, "Couldn't get strut for dock."); 304 | } else { 305 | const uint32_t* STRUT = (uint32_t*)xcb_get_property_value(STRUTREPLY); 306 | 307 | if (!STRUT) { 308 | Debug::log(ERR, "Couldn't get strut for dock. (2)"); 309 | } else { 310 | // Set the dock's align 311 | // LEFT RIGHT TOP BOTTOM 312 | // 0 1 2 3 313 | if (STRUT[2] > 0 && STRUT[3] == 0) { 314 | // top 315 | PWINDOWINARR->setDockAlign(DOCK_TOP); 316 | } else if (STRUT[2] == 0 && STRUT[3] > 0) { 317 | // bottom 318 | PWINDOWINARR->setDockAlign(DOCK_BOTTOM); 319 | } 320 | 321 | // little todo: support left/right docks 322 | } 323 | } 324 | 325 | free(STRUTREPLY); 326 | 327 | Debug::log(LOG, "New dock created, setting default XYWH to: " + std::to_string(PWINDOWINARR->getDefaultPosition().x) + ", " + std::to_string(PWINDOWINARR->getDefaultPosition().y) 328 | + ", " + std::to_string(PWINDOWINARR->getDefaultSize().x) + ", " + std::to_string(PWINDOWINARR->getDefaultSize().y)); 329 | 330 | PWINDOWINARR->setDock(true); 331 | 332 | // since it's a dock get its monitor from the coords 333 | const auto CENTERVEC = PWINDOWINARR->getDefaultPosition() + (PWINDOWINARR->getDefaultSize() / 2.f); 334 | const auto MONITOR = g_pWindowManager->getMonitorFromCoord(CENTERVEC); 335 | if (MONITOR) { 336 | PWINDOWINARR->setMonitor(MONITOR->ID); 337 | Debug::log(LOG, "Guessed dock's monitor to be " + std::to_string(MONITOR->ID) + "."); 338 | } else { 339 | Debug::log(LOG, "Couldn't guess dock's monitor. Leaving at " + std::to_string(PWINDOWINARR->getMonitor()) + "."); 340 | } 341 | } 342 | } 343 | } 344 | free(wm_type_cookiereply); 345 | // 346 | // 347 | // 348 | 349 | g_pWindowManager->getICCCMSizeHints(PWINDOWINARR); 350 | 351 | if (nextWindowCentered /* Basically means dialog */) { 352 | auto DELTA = PWINDOWINARR->getPseudoSize() - PWINDOWINARR->getDefaultSize(); 353 | 354 | // update 355 | PWINDOWINARR->setDefaultSize(PWINDOWINARR->getPseudoSize()); 356 | PWINDOWINARR->setDefaultPosition(PWINDOWINARR->getDefaultPosition() - DELTA / 2.f); 357 | } 358 | 359 | // Check the size and pos rules 360 | for (auto& rule : ConfigManager::getMatchingRules(windowID)) { 361 | if (!PWINDOWINARR->getFirstOpen()) 362 | break; 363 | 364 | if (rule.szRule.find("size") == 0) { 365 | try { 366 | const auto VALUE = rule.szRule.substr(rule.szRule.find(" ") + 1); 367 | const auto SIZEX = stoi(VALUE.substr(0, VALUE.find(" "))); 368 | const auto SIZEY = stoi(VALUE.substr(VALUE.find(" ") + 1)); 369 | 370 | Debug::log(LOG, "Rule size, applying to window " + std::to_string(windowID)); 371 | 372 | PWINDOWINARR->setDefaultSize(Vector2D(SIZEX, SIZEY)); 373 | } catch (...) { 374 | Debug::log(LOG, "Rule size failed, rule: " + rule.szRule + "=" + rule.szValue); 375 | } 376 | } else if (rule.szRule.find("move") == 0) { 377 | try { 378 | const auto VALUE = rule.szRule.substr(rule.szRule.find(" ") + 1); 379 | const auto POSX = stoi(VALUE.substr(0, VALUE.find(" "))); 380 | const auto POSY = stoi(VALUE.substr(VALUE.find(" ") + 1)); 381 | 382 | Debug::log(LOG, "Rule move, applying to window " + std::to_string(windowID)); 383 | 384 | PWINDOWINARR->setDefaultPosition(Vector2D(POSX, POSY) + g_pWindowManager->monitors[CURRENTSCREEN].vecPosition); 385 | } catch (...) { 386 | Debug::log(LOG, "Rule move failed, rule: " + rule.szRule + "=" + rule.szValue); 387 | } 388 | } 389 | } 390 | 391 | // 392 | 393 | PWINDOWINARR->setSize(PWINDOWINARR->getDefaultSize()); 394 | PWINDOWINARR->setPosition(PWINDOWINARR->getDefaultPosition()); 395 | 396 | // The anim util will take care of this. 397 | PWINDOWINARR->setEffectiveSize(PWINDOWINARR->getDefaultSize()); 398 | PWINDOWINARR->setEffectivePosition(PWINDOWINARR->getDefaultPosition()); 399 | 400 | // Also sets the old one 401 | g_pWindowManager->calculateNewWindowParams(PWINDOWINARR); 402 | 403 | Debug::log(LOG, "Created a new floating window! X: " + std::to_string(PWINDOWINARR->getPosition().x) + ", Y: " + std::to_string(PWINDOWINARR->getPosition().y) + ", W: " + std::to_string(PWINDOWINARR->getSize().x) + ", H:" + std::to_string(PWINDOWINARR->getSize().y) + " ID: " + std::to_string(windowID)); 404 | 405 | // Set map values 406 | g_pWindowManager->Values[0] = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE; 407 | xcb_change_window_attributes_checked(g_pWindowManager->DisplayConnection, windowID, XCB_CW_EVENT_MASK, g_pWindowManager->Values); 408 | 409 | // Fix docks 410 | if (PWINDOWINARR->getDock()) 411 | g_pWindowManager->recalcAllDocks(); 412 | 413 | nextWindowCentered = false; 414 | 415 | // Reset flags 416 | PWINDOWINARR->setConstructed(true); 417 | PWINDOWINARR->setFirstOpen(false); 418 | 419 | // Fullscreen rule 420 | if (PWINDOWINARR->getFullscreen()) { 421 | PWINDOWINARR->setFullscreen(false); 422 | g_pWindowManager->toggleWindowFullscrenn(PWINDOWINARR->getDrawable()); 423 | } 424 | 425 | return PWINDOWINARR; 426 | } 427 | 428 | CWindow* Events::remapWindow(int windowID, bool wasfloating, int forcemonitor) { 429 | const auto PWINDOWINARR = g_pWindowManager->getWindowFromDrawable(windowID); 430 | 431 | if (!PWINDOWINARR) { 432 | Debug::log(ERR, "remapWindow called with an invalid window!"); 433 | return nullptr; 434 | } 435 | 436 | 437 | PWINDOWINARR->setIsFloating(false); 438 | PWINDOWINARR->setDirty(true); 439 | 440 | auto PMONITOR = g_pWindowManager->getMonitorFromCursor(); 441 | if (!PMONITOR) { 442 | Debug::log(ERR, "Monitor was null! (remapWindow) Using 0."); 443 | PMONITOR = &g_pWindowManager->monitors[0]; 444 | 445 | if (g_pWindowManager->monitors.size() == 0) { 446 | Debug::log(ERR, "Not continuing. Monitors size 0."); 447 | return nullptr; 448 | } 449 | } 450 | 451 | // Check the monitor rule 452 | for (auto& rule : ConfigManager::getMatchingRules(windowID)) { 453 | if (!PWINDOWINARR->getFirstOpen()) 454 | break; 455 | 456 | if (rule.szRule.find("monitor") == 0) { 457 | try { 458 | const auto MONITOR = stoi(rule.szRule.substr(rule.szRule.find(" ") + 1)); 459 | 460 | Debug::log(LOG, "Rule monitor, applying to window " + std::to_string(windowID)); 461 | 462 | if (MONITOR > g_pWindowManager->monitors.size() || MONITOR < 0) 463 | forcemonitor = -1; 464 | else 465 | forcemonitor = MONITOR; 466 | } catch (...) { 467 | Debug::log(LOG, "Rule monitor failed, rule: " + rule.szRule + "=" + rule.szValue); 468 | } 469 | } 470 | 471 | if (rule.szRule.find("pseudo") == 0) { 472 | PWINDOWINARR->setIsPseudotiled(true); 473 | } 474 | 475 | if (rule.szRule.find("fullscreen") == 0) { 476 | PWINDOWINARR->setFullscreen(true); 477 | } 478 | 479 | if (rule.szRule.find("workspace") == 0) { 480 | try { 481 | const auto WORKSPACE = stoi(rule.szRule.substr(rule.szRule.find(" ") + 1)); 482 | 483 | Debug::log(LOG, "Rule workspace, applying to window " + std::to_string(windowID)); 484 | 485 | g_pWindowManager->changeWorkspaceByID(WORKSPACE); 486 | forcemonitor = g_pWindowManager->getWorkspaceByID(WORKSPACE)->getMonitor(); 487 | } catch (...) { 488 | Debug::log(LOG, "Rule workspace failed, rule: " + rule.szRule + "=" + rule.szValue); 489 | } 490 | } 491 | } 492 | 493 | if (g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow) && forcemonitor == -1 && PMONITOR->ID != g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow)->getMonitor()) { 494 | // If the monitor of the last window doesnt match the current screen force the monitor of the cursor 495 | forcemonitor = PMONITOR->ID; 496 | } 497 | 498 | const auto CURRENTSCREEN = forcemonitor != -1 ? forcemonitor : PMONITOR->ID; 499 | PWINDOWINARR->setWorkspaceID(g_pWindowManager->activeWorkspaces[CURRENTSCREEN]); 500 | PWINDOWINARR->setMonitor(CURRENTSCREEN); 501 | 502 | // Window name 503 | const auto WINNAME = getWindowName(windowID); 504 | Debug::log(LOG, "New window got name: " + WINNAME); 505 | PWINDOWINARR->setName(WINNAME); 506 | 507 | const auto WINCLASSNAME = getClassName(windowID); 508 | Debug::log(LOG, "New window got class: " + WINCLASSNAME.second); 509 | PWINDOWINARR->setClassName(WINCLASSNAME.second); 510 | 511 | // For all floating windows, get their default size 512 | const auto GEOMETRYCOOKIE = xcb_get_geometry(g_pWindowManager->DisplayConnection, windowID); 513 | const auto GEOMETRY = xcb_get_geometry_reply(g_pWindowManager->DisplayConnection, GEOMETRYCOOKIE, 0); 514 | 515 | if (GEOMETRY) { 516 | PWINDOWINARR->setDefaultPosition(Vector2D(GEOMETRY->x, GEOMETRY->y)); 517 | PWINDOWINARR->setDefaultSize(Vector2D(GEOMETRY->width, GEOMETRY->height)); 518 | } else { 519 | Debug::log(ERR, "Geometry failed in remap."); 520 | 521 | PWINDOWINARR->setDefaultPosition(Vector2D(0, 0)); 522 | PWINDOWINARR->setDefaultSize(Vector2D(g_pWindowManager->Screen->width_in_pixels / 2.f, g_pWindowManager->Screen->height_in_pixels / 2.f)); 523 | } 524 | 525 | // Check if the workspace has a fullscreen window. if so, remove its' fullscreen status. 526 | const auto PWORKSPACE = g_pWindowManager->getWorkspaceByID(g_pWindowManager->activeWorkspaces[CURRENTSCREEN]); 527 | if (PWORKSPACE && PWORKSPACE->getHasFullscreenWindow()) { 528 | const auto PFULLSCREENWINDOW = g_pWindowManager->getFullscreenWindowByWorkspace(PWORKSPACE->getID()); 529 | 530 | if (PFULLSCREENWINDOW) { 531 | PFULLSCREENWINDOW->setFullscreen(false); 532 | PFULLSCREENWINDOW->setDirty(true); 533 | PWORKSPACE->setHasFullscreenWindow(false); 534 | g_pWindowManager->setAllWorkspaceWindowsDirtyByID(PWORKSPACE->getID()); 535 | } 536 | } 537 | 538 | g_pWindowManager->getICCCMSizeHints(PWINDOWINARR); 539 | 540 | // Set the parent 541 | // check if lastwindow is on our workspace 542 | if (auto PLASTWINDOW = g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow); (PLASTWINDOW && PLASTWINDOW->getWorkspaceID() == g_pWindowManager->activeWorkspaces[CURRENTSCREEN]) || wasfloating || (forcemonitor != -1 && forcemonitor != PMONITOR->ID) || PWINDOWINARR->getWorkspaceID() == SCRATCHPAD_ID) { 543 | // LastWindow is on our workspace, let's make a new split node 544 | 545 | if (PWINDOWINARR->getWorkspaceID() == SCRATCHPAD_ID) 546 | PLASTWINDOW = g_pWindowManager->findPreferredOnScratchpad(); 547 | else { 548 | if (wasfloating || (forcemonitor != -1 && forcemonitor != PMONITOR->ID) || (forcemonitor != -1 && PLASTWINDOW->getWorkspaceID() != g_pWindowManager->activeWorkspaces[CURRENTSCREEN]) || PLASTWINDOW->getIsFloating()) { 549 | // if it's force monitor, find the first on a workspace. 550 | if ((forcemonitor != -1 && forcemonitor != PMONITOR->ID) || (forcemonitor != -1 && PLASTWINDOW->getWorkspaceID() != g_pWindowManager->activeWorkspaces[CURRENTSCREEN])) { 551 | PLASTWINDOW = g_pWindowManager->findFirstWindowOnWorkspace(g_pWindowManager->activeWorkspaces[CURRENTSCREEN]); 552 | } else { 553 | // find a window manually by the cursor 554 | PLASTWINDOW = g_pWindowManager->findWindowAtCursor(); 555 | } 556 | } 557 | } 558 | 559 | if (PLASTWINDOW && PLASTWINDOW->getDrawable() != windowID) { 560 | CWindow newWindowSplitNode; 561 | newWindowSplitNode.setPosition(PLASTWINDOW->getPosition()); 562 | newWindowSplitNode.setSize(PLASTWINDOW->getSize()); 563 | 564 | newWindowSplitNode.setChildNodeAID(PLASTWINDOW->getDrawable()); 565 | newWindowSplitNode.setChildNodeBID(windowID); 566 | 567 | newWindowSplitNode.setParentNodeID(PLASTWINDOW->getParentNodeID()); 568 | 569 | newWindowSplitNode.setWorkspaceID(PWINDOWINARR->getWorkspaceID()); 570 | newWindowSplitNode.setMonitor(PWINDOWINARR->getMonitor()); 571 | 572 | // generates a negative node ID 573 | newWindowSplitNode.generateNodeID(); 574 | 575 | // update the parent if exists 576 | if (const auto PREVPARENT = g_pWindowManager->getWindowFromDrawable(PLASTWINDOW->getParentNodeID()); PREVPARENT) { 577 | if (PREVPARENT->getChildNodeAID() == PLASTWINDOW->getDrawable()) { 578 | PREVPARENT->setChildNodeAID(newWindowSplitNode.getDrawable()); 579 | } else { 580 | PREVPARENT->setChildNodeBID(newWindowSplitNode.getDrawable()); 581 | } 582 | } 583 | 584 | PWINDOWINARR->setParentNodeID(newWindowSplitNode.getDrawable()); 585 | PLASTWINDOW->setParentNodeID(newWindowSplitNode.getDrawable()); 586 | 587 | g_pWindowManager->addWindowToVectorSafe(newWindowSplitNode); 588 | } else { 589 | PWINDOWINARR->setParentNodeID(0); 590 | } 591 | } else { 592 | // LastWindow is not on our workspace, so set the parent to 0. 593 | PWINDOWINARR->setParentNodeID(0); 594 | } 595 | 596 | // For master layout, add the index 597 | PWINDOWINARR->setMasterChildIndex(g_pWindowManager->getWindowsOnWorkspace(g_pWindowManager->activeWorkspaces[CURRENTSCREEN]) - 1); 598 | // and set master if needed 599 | if (g_pWindowManager->getWindowsOnWorkspace(g_pWindowManager->activeWorkspaces[CURRENTSCREEN]) == 1) // 1 because the current window is already in the arr 600 | PWINDOWINARR->setMaster(true); 601 | 602 | 603 | // Also sets the old one 604 | g_pWindowManager->calculateNewWindowParams(PWINDOWINARR); 605 | 606 | // Set real size. No animations in the beginning. Maybe later. TODO? 607 | PWINDOWINARR->setRealPosition(PWINDOWINARR->getEffectivePosition()); 608 | PWINDOWINARR->setRealSize(PWINDOWINARR->getEffectiveSize()); 609 | 610 | Debug::log(LOG, "Created a new tiled window! X: " + std::to_string(PWINDOWINARR->getPosition().x) + ", Y: " + std::to_string(PWINDOWINARR->getPosition().y) + ", W: " + std::to_string(PWINDOWINARR->getSize().x) + ", H:" + std::to_string(PWINDOWINARR->getSize().y) + " ID: " + std::to_string(windowID)); 611 | 612 | // Set map values 613 | g_pWindowManager->Values[0] = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE; 614 | xcb_change_window_attributes_checked(g_pWindowManager->DisplayConnection, windowID, XCB_CW_EVENT_MASK, g_pWindowManager->Values); 615 | 616 | // make the last window top (animations look better) 617 | g_pWindowManager->setAWindowTop(g_pWindowManager->LastWindow); 618 | 619 | // Focus 620 | g_pWindowManager->setFocusedWindow(windowID); 621 | 622 | // Reset flags 623 | PWINDOWINARR->setConstructed(true); 624 | PWINDOWINARR->setFirstOpen(false); 625 | 626 | // Fullscreen rule 627 | if (PWINDOWINARR->getFullscreen()) { 628 | PWINDOWINARR->setFullscreen(false); 629 | g_pWindowManager->toggleWindowFullscrenn(PWINDOWINARR->getDrawable()); 630 | } 631 | 632 | return PWINDOWINARR; 633 | } 634 | 635 | void Events::eventMapWindow(xcb_generic_event_t* event) { 636 | const auto E = reinterpret_cast(event); 637 | 638 | // Ignore sequence 639 | ignoredEvents.push_back(E->sequence); 640 | 641 | // let bar check if it wasnt a tray item 642 | if (g_pWindowManager->statusBar) 643 | g_pWindowManager->statusBar->ensureTrayClientHidden(E->window, false); 644 | 645 | RETURNIFBAR; 646 | 647 | // Map the window 648 | xcb_map_window(g_pWindowManager->DisplayConnection, E->window); 649 | 650 | // We check if the window is not on our tile-blacklist and if it is, we have a special treatment procedure for it. 651 | // this func also sets some stuff 652 | 653 | // Check if it's not unmapped 654 | CWindow* pNewWindow = nullptr; 655 | if (g_pWindowManager->isWindowUnmapped(E->window)) { 656 | Debug::log(LOG, "Window was unmapped, mapping back."); 657 | g_pWindowManager->moveWindowToMapped(E->window); 658 | 659 | pNewWindow = g_pWindowManager->getWindowFromDrawable(E->window); 660 | } else { 661 | if (g_pWindowManager->getWindowFromDrawable(E->window)) { 662 | Debug::log(LOG, "Window already managed."); 663 | return; 664 | } 665 | 666 | if (!g_pWindowManager->shouldBeManaged(E->window)) { 667 | Debug::log(LOG, "window shouldn't be managed"); 668 | return; 669 | } 670 | 671 | if (g_pWindowManager->scratchpadActive) { 672 | KeybindManager::toggleScratchpad(""); 673 | 674 | const auto PNEW = g_pWindowManager->findWindowAtCursor(); 675 | g_pWindowManager->LastWindow = PNEW ? PNEW->getDrawable() : 0; 676 | } 677 | 678 | CWindow window; 679 | window.setDrawable(E->window); 680 | g_pWindowManager->addWindowToVectorSafe(window); 681 | 682 | if (g_pWindowManager->shouldBeFloatedOnInit(E->window)) { 683 | Debug::log(LOG, "Window SHOULD be floating on start."); 684 | pNewWindow = remapFloatingWindow(E->window); 685 | } else { 686 | Debug::log(LOG, "Window should NOT be floating on start."); 687 | pNewWindow = remapWindow(E->window); 688 | } 689 | } 690 | 691 | if (!pNewWindow) { 692 | Debug::log(LOG, "Removing, NULL."); 693 | g_pWindowManager->removeWindowFromVectorSafe(E->window); 694 | return; 695 | } 696 | 697 | // Do post-creation checks. 698 | g_pWindowManager->doPostCreationChecks(pNewWindow); 699 | 700 | // Do ICCCM 701 | g_pWindowManager->getICCCMWMProtocols(pNewWindow); 702 | 703 | // Do transient checks 704 | EWMH::checkTransient(E->window); 705 | 706 | // Make all floating windows above 707 | g_pWindowManager->setAllFloatingWindowsTop(); 708 | 709 | // Set not under 710 | pNewWindow->setUnderFullscreen(false); 711 | pNewWindow->setDirty(true); 712 | 713 | // EWMH 714 | EWMH::updateClientList(); 715 | EWMH::setFrameExtents(E->window); 716 | EWMH::updateWindow(E->window); 717 | } 718 | 719 | void Events::eventButtonPress(xcb_generic_event_t* event) { 720 | const auto E = reinterpret_cast(event); 721 | 722 | RETURNIFBAR; 723 | 724 | // mouse down! 725 | g_pWindowManager->mouseKeyDown = E->detail; 726 | 727 | if (const auto PLASTWINDOW = g_pWindowManager->getWindowFromDrawable(g_pWindowManager->LastWindow); PLASTWINDOW) { 728 | 729 | if (E->detail != 3) 730 | PLASTWINDOW->setDraggingTiled(!PLASTWINDOW->getIsFloating()); 731 | 732 | g_pWindowManager->actingOnWindowFloating = PLASTWINDOW->getDrawable(); 733 | g_pWindowManager->mouseLastPos = g_pWindowManager->getCursorPos(); 734 | 735 | if (!PLASTWINDOW->getIsFloating()) { 736 | const auto PDRAWABLE = PLASTWINDOW->getDrawable(); 737 | if (E->detail != 3) // right click (resize) does not 738 | KeybindManager::toggleActiveWindowFloating(""); 739 | 740 | // refocus 741 | g_pWindowManager->setFocusedWindow(PDRAWABLE); 742 | } 743 | } 744 | 745 | xcb_grab_pointer(g_pWindowManager->DisplayConnection, 0, g_pWindowManager->Screen->root, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION_HINT, 746 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, 747 | g_pWindowManager->Screen->root, XCB_NONE, XCB_CURRENT_TIME); 748 | } 749 | 750 | void Events::eventButtonRelease(xcb_generic_event_t* event) { 751 | const auto E = reinterpret_cast(event); 752 | 753 | RETURNIFBAR; 754 | 755 | const auto PACTINGWINDOW = g_pWindowManager->getWindowFromDrawable(g_pWindowManager->actingOnWindowFloating); 756 | 757 | // ungrab the mouse ptr 758 | xcb_ungrab_pointer(g_pWindowManager->DisplayConnection, XCB_CURRENT_TIME); 759 | 760 | if (PACTINGWINDOW) { 761 | PACTINGWINDOW->setDirty(true); 762 | 763 | if (PACTINGWINDOW->getDraggingTiled()) { 764 | g_pWindowManager->LastWindow = PACTINGWINDOW->getDrawable(); 765 | KeybindManager::toggleActiveWindowFloating(""); 766 | } 767 | 768 | } 769 | 770 | g_pWindowManager->actingOnWindowFloating = 0; 771 | g_pWindowManager->mouseKeyDown = 0; 772 | } 773 | 774 | void Events::eventKeyPress(xcb_generic_event_t* event) { 775 | const auto E = reinterpret_cast(event); 776 | 777 | RETURNIFBAR; 778 | 779 | const auto KEYSYM = KeybindManager::getKeysymFromKeycode(E->detail); 780 | const auto IGNOREDMOD = KeybindManager::modToMask(ConfigManager::getString("ignore_mod")); 781 | 782 | for (auto& keybind : KeybindManager::keybinds) { 783 | if (keybind.getKeysym() != 0 && keybind.getKeysym() == KEYSYM && ((keybind.getMod() == E->state) || ((keybind.getMod() | IGNOREDMOD) == E->state))) { 784 | keybind.getDispatcher()(keybind.getCommand()); 785 | return; 786 | // TODO: fix duplicating keybinds 787 | } 788 | } 789 | } 790 | 791 | void Events::eventMotionNotify(xcb_generic_event_t* event) { 792 | const auto E = reinterpret_cast(event); 793 | 794 | RETURNIFBAR; 795 | 796 | if (!g_pWindowManager->mouseKeyDown) 797 | return; // mouse up. 798 | 799 | if (!g_pWindowManager->actingOnWindowFloating) 800 | return; // not acting, return. 801 | 802 | // means we are holding super 803 | const auto POINTERPOS = g_pWindowManager->getCursorPos(); 804 | const auto POINTERDELTA = Vector2D(POINTERPOS) - g_pWindowManager->mouseLastPos; 805 | 806 | const auto PACTINGWINDOW = g_pWindowManager->getWindowFromDrawable(g_pWindowManager->actingOnWindowFloating); 807 | 808 | if (!PACTINGWINDOW) { 809 | Debug::log(ERR, "ActingWindow not null but doesn't exist?? (Died?)"); 810 | g_pWindowManager->actingOnWindowFloating = 0; 811 | return; 812 | } 813 | 814 | if (abs(POINTERDELTA.x) < 1 && abs(POINTERDELTA.y) < 1) 815 | return; // micromovements 816 | 817 | if (g_pWindowManager->mouseKeyDown == 1) { 818 | // moving 819 | PACTINGWINDOW->setPosition(PACTINGWINDOW->getPosition() + POINTERDELTA); 820 | PACTINGWINDOW->setEffectivePosition(PACTINGWINDOW->getPosition()); 821 | PACTINGWINDOW->setDefaultPosition(PACTINGWINDOW->getPosition()); 822 | PACTINGWINDOW->setRealPosition(PACTINGWINDOW->getPosition()); 823 | 824 | // update workspace if needed 825 | if (g_pWindowManager->getMonitorFromCursor()) { 826 | const auto WORKSPACE = g_pWindowManager->activeWorkspaces[g_pWindowManager->getMonitorFromCursor()->ID]; 827 | PACTINGWINDOW->setWorkspaceID(WORKSPACE); 828 | } else { 829 | Debug::log(WARN, "Monitor was nullptr! Ignoring workspace change in MouseMoveEvent."); 830 | } 831 | 832 | PACTINGWINDOW->setDirty(true); 833 | } else if (g_pWindowManager->mouseKeyDown == 3) { 834 | 835 | if (!PACTINGWINDOW->getIsFloating()) { 836 | g_pWindowManager->processCursorDeltaOnWindowResizeTiled(PACTINGWINDOW, POINTERDELTA); 837 | } else { 838 | // resizing 839 | PACTINGWINDOW->setSize(PACTINGWINDOW->getSize() + POINTERDELTA); 840 | // clamp 841 | PACTINGWINDOW->setSize(Vector2D(std::clamp(PACTINGWINDOW->getSize().x, (double)30, (double)999999), std::clamp(PACTINGWINDOW->getSize().y, (double)30, (double)999999))); 842 | 843 | // apply to other 844 | PACTINGWINDOW->setDefaultSize(PACTINGWINDOW->getSize()); 845 | PACTINGWINDOW->setEffectiveSize(PACTINGWINDOW->getSize()); 846 | PACTINGWINDOW->setRealSize(PACTINGWINDOW->getSize()); 847 | PACTINGWINDOW->setPseudoSize(PACTINGWINDOW->getSize()); 848 | } 849 | 850 | PACTINGWINDOW->setDirty(true); 851 | } 852 | 853 | g_pWindowManager->mouseLastPos = POINTERPOS; 854 | } 855 | 856 | void Events::eventExpose(xcb_generic_event_t* event) { 857 | const auto E = reinterpret_cast(event); 858 | 859 | // nothing 860 | } 861 | 862 | void Events::eventClientMessage(xcb_generic_event_t* event) { 863 | const auto E = reinterpret_cast(event); 864 | 865 | if (!g_pWindowManager->statusBar) 866 | g_pWindowManager->handleClientMessage(E); // Client message handling 867 | 868 | RETURNIFMAIN; // Only for the bar 869 | 870 | // Tray clients 871 | 872 | if (E->type == HYPRATOMS["_NET_SYSTEM_TRAY_OPCODE"] && E->format == 32) { 873 | // Tray request! 874 | 875 | Debug::log(LOG, "Docking a window to the tray!"); 876 | 877 | if (E->data.data32[1] == 0) { // Request dock 878 | const xcb_window_t CLIENT = E->data.data32[2]; 879 | 880 | uint32_t values[3] = {0,0,0}; 881 | 882 | values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_RESIZE_REDIRECT; 883 | 884 | xcb_change_window_attributes(g_pWindowManager->DisplayConnection, CLIENT, 885 | XCB_CW_EVENT_MASK, values); 886 | 887 | // get XEMBED 888 | 889 | const auto XEMBEDCOOKIE = xcb_get_property(g_pWindowManager->DisplayConnection, 0, CLIENT, HYPRATOMS["_XEMBED_INFO"], 890 | XCB_GET_PROPERTY_TYPE_ANY, 0, 64); 891 | 892 | xcb_generic_error_t* err; 893 | const auto XEMBEDREPLY = xcb_get_property_reply(g_pWindowManager->DisplayConnection, XEMBEDCOOKIE, &err); 894 | 895 | if (!XEMBEDREPLY || err || XEMBEDREPLY->length == 0) { 896 | Debug::log(ERR, "Tray dock opcode recieved with no XEmbed?"); 897 | if (err) 898 | Debug::log(ERR, "Error code: " + std::to_string(err->error_code)); 899 | free(XEMBEDREPLY); 900 | return; 901 | } 902 | 903 | const uint32_t* XEMBEDPROP = (uint32_t*)xcb_get_property_value(XEMBEDREPLY); 904 | Debug::log(LOG, "XEmbed recieved with format " + std::to_string(XEMBEDREPLY->format) + ", length " + std::to_string(XEMBEDREPLY->length) 905 | + ", version " + std::to_string(XEMBEDPROP[0]) + ", flags " + std::to_string(XEMBEDPROP[1])); 906 | 907 | const auto XEMBEDVERSION = XEMBEDPROP[0] > 1 ? 1 : XEMBEDPROP[0]; 908 | 909 | free(XEMBEDREPLY); 910 | 911 | xcb_reparent_window(g_pWindowManager->DisplayConnection, CLIENT, g_pWindowManager->statusBar->getWindowID(), 0, 0); 912 | 913 | // icon sizes are barY - 2 - pad: 1 914 | values[0] = ConfigManager::getInt("bar:height") - 2 < 1 ? 1 : ConfigManager::getInt("bar:height") - 2; 915 | values[1] = values[0]; 916 | 917 | xcb_configure_window(g_pWindowManager->DisplayConnection, CLIENT, XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_WIDTH, values); 918 | 919 | 920 | // Notify the thing we did it 921 | uint8_t buf[32] = {NULL}; 922 | xcb_client_message_event_t* event = (xcb_client_message_event_t*)buf; 923 | event->response_type = XCB_CLIENT_MESSAGE; 924 | event->window = CLIENT; 925 | event->type = HYPRATOMS["_XEMBED"]; 926 | event->format = 32; 927 | event->data.data32[0] = XCB_CURRENT_TIME; 928 | event->data.data32[1] = 0; 929 | event->data.data32[2] = g_pWindowManager->statusBar->getWindowID(); 930 | event->data.data32[3] = XEMBEDVERSION; 931 | xcb_send_event(g_pWindowManager->DisplayConnection, 0, CLIENT, XCB_EVENT_MASK_NO_EVENT, (char*)event); 932 | 933 | // put it into the save set 934 | xcb_change_save_set(g_pWindowManager->DisplayConnection, XCB_SET_MODE_INSERT, CLIENT); 935 | 936 | // make a tray client 937 | CTrayClient newTrayClient; 938 | newTrayClient.window = CLIENT; 939 | newTrayClient.XEVer = XEMBEDVERSION; 940 | 941 | g_pWindowManager->trayclients.push_back(newTrayClient); 942 | 943 | xcb_map_window(g_pWindowManager->DisplayConnection, CLIENT); 944 | } 945 | } 946 | } 947 | 948 | void Events::eventConfigure(xcb_generic_event_t* event) { 949 | const auto E = reinterpret_cast(event); 950 | 951 | Debug::log(LOG, "Window " + std::to_string(E->window) + " requests XY: " + std::to_string(E->x) + ", " + std::to_string(E->y) + ", WH: " + std::to_string(E->width) + "x" + std::to_string(E->height)); 952 | 953 | auto *const PWINDOW = g_pWindowManager->getWindowFromDrawable(E->window); 954 | 955 | if (!PWINDOW) { 956 | Debug::log(LOG, "CONFIGURE: Window doesn't exist, ignoring."); 957 | return; 958 | } 959 | 960 | if (!PWINDOW->getIsFloating()) { 961 | Debug::log(LOG, "CONFIGURE: Window isn't floating, ignoring."); 962 | return; 963 | } 964 | 965 | PWINDOW->setDefaultPosition(Vector2D(E->x, E->y)); 966 | PWINDOW->setDefaultSize(Vector2D(E->width, E->height)); 967 | PWINDOW->setEffectiveSize(PWINDOW->getDefaultSize()); 968 | PWINDOW->setEffectivePosition(PWINDOW->getDefaultPosition()); 969 | } 970 | 971 | void Events::eventRandRScreenChange(xcb_generic_event_t* event) { 972 | 973 | // fix sus randr events, that sometimes happen 974 | // it will spam these for no reason 975 | // so we check if we have > 9 consecutive randr events less than 1s between each 976 | // and if so, we stop listening for them 977 | const auto DELTA = std::chrono::duration_cast(lastRandREvent - std::chrono::high_resolution_clock::now()); 978 | 979 | if (susRandREventNo < 10) { 980 | if (DELTA.count() <= 1000) { 981 | susRandREventNo += 1; 982 | Debug::log(WARN, "Suspicious RandR event no. " + std::to_string(susRandREventNo) + "!"); 983 | if (susRandREventNo > 9) 984 | Debug::log(WARN, "Disabling RandR event listening because of excess suspicious RandR events (bug!)"); 985 | } 986 | else 987 | susRandREventNo = 0; 988 | } 989 | 990 | if (susRandREventNo > 9) 991 | return; 992 | // randr sus fixed 993 | // 994 | 995 | // redetect screens 996 | g_pWindowManager->monitors.clear(); 997 | g_pWindowManager->setupRandrMonitors(); 998 | 999 | // Detect monitors that are incorrect 1000 | // Orphaned workspaces 1001 | for (auto& w : g_pWindowManager->workspaces) { 1002 | if (w.getMonitor() >= g_pWindowManager->monitors.size()) 1003 | w.setMonitor(0); 1004 | } 1005 | 1006 | // Empty monitors 1007 | bool fineMonitors[g_pWindowManager->monitors.size()]; 1008 | for (int i = 0; i < g_pWindowManager->monitors.size(); ++i) 1009 | fineMonitors[i] = false; 1010 | 1011 | for (auto& w : g_pWindowManager->workspaces) { 1012 | fineMonitors[w.getMonitor()] = true; 1013 | } 1014 | 1015 | for (int i = 0; i < g_pWindowManager->monitors.size(); ++i) { 1016 | if (!fineMonitors[i]) { 1017 | // add a workspace 1018 | CWorkspace newWorkspace; 1019 | newWorkspace.setMonitor(i); 1020 | newWorkspace.setID(g_pWindowManager->getHighestWorkspaceID() + 1); 1021 | newWorkspace.setHasFullscreenWindow(false); 1022 | newWorkspace.setLastWindow(0); 1023 | g_pWindowManager->workspaces.push_back(newWorkspace); 1024 | } 1025 | } 1026 | 1027 | // reload the config to update the bar too 1028 | ConfigManager::loadConfigLoadVars(); 1029 | 1030 | // Make all windows dirty and recalc all workspaces 1031 | g_pWindowManager->recalcAllWorkspaces(); 1032 | } --------------------------------------------------------------------------------