#include "Bar.hpp" #include #include #include "../config/ConfigManager.hpp" #include "../windowManager.hpp" bool isParentDead() { const auto PPID = getppid(); return PPID == 1; } void parseEvent() { while(1) { g_pWindowManager->recieveEvent(); } } int64_t barMainThread() { // Main already created all the pipes Debug::log(LOG, "Child says Hello World!"); // Well now this is the init // it's pretty tricky because we only need to init the stuff we need g_pWindowManager->DisplayConnection = xcb_connect(NULL, &barScreen); if (const auto RET = xcb_connection_has_error(g_pWindowManager->DisplayConnection); RET != 0) { Debug::log(CRIT, "Connection Failed! Return: " + std::to_string(RET)); return RET; } // Screen g_pWindowManager->Screen = xcb_aux_get_screen(g_pWindowManager->DisplayConnection, barScreen); if (!g_pWindowManager->Screen) { Debug::log(CRIT, "Screen was null!"); return 1; } Debug::log(LOG, "Bar init Phase 1 done."); // Init atoms // get atoms for (auto& ATOM : HYPRATOMS) { xcb_intern_atom_cookie_t cookie = xcb_intern_atom(g_pWindowManager->DisplayConnection, 0, ATOM.first.length(), ATOM.first.c_str()); xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(g_pWindowManager->DisplayConnection, cookie, NULL); if (!reply) { Debug::log(ERR, "Atom failed: " + ATOM.first); continue; } ATOM.second = reply->atom; } Debug::log(LOG, "Atoms done."); g_pWindowManager->EWMHConnection = (xcb_ewmh_connection_t*)malloc(sizeof(xcb_ewmh_connection_t)); xcb_ewmh_init_atoms_replies(g_pWindowManager->EWMHConnection, xcb_ewmh_init_atoms(g_pWindowManager->DisplayConnection, g_pWindowManager->EWMHConnection), nullptr); Debug::log(LOG, "Bar init EWMH done."); // Init randr for monitors. g_pWindowManager->setupRandrMonitors(); // Init depth g_pWindowManager->setupColormapAndStuff(); // Setup our bar CStatusBar STATUSBAR; // Tell everyone we are in the child process. g_pWindowManager->statusBar = &STATUSBAR; Debug::log(LOG, "Bar init Phase 2 done."); // Start the parse event thread std::thread([=]() { parseEvent(); }).detach(); // Init config manager ConfigManager::init(); if (ConfigManager::getInt("bar:enabled") == 1) { Debug::log(LOG, "Bar enabled, reload config to launch it."); ConfigManager::loadConfigLoadVars(); } Debug::log(LOG, "Bar setup finished!"); int lazyUpdateCounter = 0; // setup the tray so apps send to us if (!STATUSBAR.getIsDestroyed()) STATUSBAR.setupTray(); while (1) { // Don't spam these if (lazyUpdateCounter > 10) { ConfigManager::tick(); lazyUpdateCounter = 0; } ++lazyUpdateCounter; // Recieve the message and send our reply IPCRecieveMessageB(g_pWindowManager->m_sIPCBarPipeIn.szPipeName); SIPCMessageBarToMain message; message.windowID = STATUSBAR.getWindowID(); IPCSendMessage(g_pWindowManager->m_sIPCBarPipeOut.szPipeName, message); // // draw the bar STATUSBAR.draw(); if (isParentDead()) { // Just for debugging SIPCMessageBarToMain message; message.windowID = 0; IPCSendMessage(g_pWindowManager->m_sIPCBarPipeOut.szPipeName, message); Debug::log(LOG, "Bar parent died!"); return 0; // If the parent died, kill the bar too. } std::this_thread::sleep_for(std::chrono::milliseconds(1000 / ConfigManager::getInt("max_fps"))); } return 0; } void CStatusBar::setupModule(SBarModule* module) { uint32_t values[2]; module->bgcontext = xcb_generate_id(g_pWindowManager->DisplayConnection); values[0] = module->bgcolor; values[1] = module->bgcolor; xcb_create_gc(g_pWindowManager->DisplayConnection, module->bgcontext, m_iPixmap, XCB_GC_BACKGROUND | XCB_GC_FOREGROUND, values); } void CStatusBar::destroyModule(SBarModule* module) { if (module->bgcontext) xcb_free_gc(g_pWindowManager->DisplayConnection, module->bgcontext); // delete it from the heap delete module; } void CStatusBar::setupTray() { Debug::log(LOG, "Setting up tray!"); char atomName[strlen("_NET_SYSTEM_TRAY_S") + 11]; snprintf(atomName, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", barScreen); // init the atom const auto TRAYCOOKIE = xcb_intern_atom(g_pWindowManager->DisplayConnection, 0, strlen(atomName), atomName); trayWindowID = xcb_generate_id(g_pWindowManager->DisplayConnection); uint32_t values[] = {g_pWindowManager->Screen->black_pixel, g_pWindowManager->Screen->black_pixel, 1, g_pWindowManager->Colormap}; xcb_create_window(g_pWindowManager->DisplayConnection, g_pWindowManager->Depth, trayWindowID, g_pWindowManager->Screen->root, -1, -1, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, g_pWindowManager->VisualType->visual_id, XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_COLORMAP, values); xcb_atom_t dockAtom[] = {HYPRATOMS["_NET_WM_WINDOW_TYPE_DOCK"]}; xcb_ewmh_set_wm_window_type(g_pWindowManager->EWMHConnection, trayWindowID, 1, dockAtom); const uint32_t ORIENTATION = 0; // Horizontal xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, trayWindowID, HYPRATOMS["_NET_SYSTEM_TRAY_ORIENTATION"], XCB_ATOM_CARDINAL, 32, 1, &ORIENTATION); xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, trayWindowID, HYPRATOMS["_NET_SYSTEM_TRAY_VISUAL"], XCB_ATOM_VISUALID, 32, 1, &g_pWindowManager->VisualType->visual_id); // COLORS // Check if the tray module is active SBarModule* pBarModule = nullptr; for (auto& mod : modules) { if (mod->value == "tray") { pBarModule = mod; break; } } if (pBarModule) { // init colors const auto R = (uint16_t)(RED(pBarModule->bgcolor) * 255.f); const auto G = (uint16_t)(GREEN(pBarModule->bgcolor) * 255.f); const auto B = (uint16_t)(BLUE(pBarModule->bgcolor) * 255.f); const unsigned short TRAYCOLORS[] = { R, G, B, R, G, B, R, G, B, R, G, B // Foreground, Error, Warning, Success }; xcb_change_property(g_pWindowManager->DisplayConnection, XCB_PROP_MODE_REPLACE, trayWindowID, HYPRATOMS["_NET_SYSTEM_TRAY_COLORS"], XCB_ATOM_CARDINAL, 32, 12, TRAYCOLORS); } // const auto TRAYREPLY = xcb_intern_atom_reply(g_pWindowManager->DisplayConnection, TRAYCOOKIE, NULL); if (!TRAYREPLY) { Debug::log(ERR, "Tray reply NULL! Aborting tray..."); free(TRAYREPLY); return; } // set the owner and check xcb_set_selection_owner(g_pWindowManager->DisplayConnection, trayWindowID, TRAYREPLY->atom, XCB_CURRENT_TIME); const auto SELCOOKIE = xcb_get_selection_owner(g_pWindowManager->DisplayConnection, TRAYREPLY->atom); const auto SELREPLY = xcb_get_selection_owner_reply(g_pWindowManager->DisplayConnection, SELCOOKIE, NULL); if (!SELREPLY) { Debug::log(ERR, "Selection owner reply NULL! Aborting tray..."); free(SELREPLY); free(TRAYREPLY); return; } if (SELREPLY->owner != trayWindowID) { Debug::log(ERR, "Couldn't set the Tray owner, maybe a different tray is running??"); free(SELREPLY); free(TRAYREPLY); return; } free(SELREPLY); free(TRAYREPLY); Debug::log(LOG, "Tray setup done, sending message!"); uint8_t buf[32] = {NULL}; xcb_client_message_event_t* event = (xcb_client_message_event_t*)buf; event->response_type = XCB_CLIENT_MESSAGE; event->window = g_pWindowManager->Screen->root; event->type = HYPRATOMS["MANAGER"]; event->format = 32; event->data.data32[0] = 0L; event->data.data32[1] = TRAYREPLY->atom; event->data.data32[2] = trayWindowID; xcb_send_event(g_pWindowManager->DisplayConnection, 0, g_pWindowManager->Screen->root, 0xFFFFFF, (char*)buf); Debug::log(LOG, "Tray message sent!"); } void CStatusBar::fixTrayOnCreate() { if (m_bHasTray && ConfigManager::getInt("bar:no_tray_saving") == 0) { for (auto& tray : g_pWindowManager->trayclients) { xcb_reparent_window(g_pWindowManager->DisplayConnection, tray.window, g_pWindowManager->statusBar->trayWindowID, 0, 0); xcb_map_window(g_pWindowManager->DisplayConnection, tray.window); tray.hidden = false; } } else { uint32_t values[2]; values[0] = 0; values[1] = 0; for (auto& tray : g_pWindowManager->trayclients) { tray.hidden = true; values[0] = 0; values[1] = 0; xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); values[0] = 30000; values[1] = 30000; xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); } } } void CStatusBar::saveTrayOnDestroy() { // TODO: fix this instead of disabling it. if (ConfigManager::getInt("bar:no_tray_saving") == 1) return; for (auto& tray : g_pWindowManager->trayclients) { xcb_reparent_window(g_pWindowManager->DisplayConnection, tray.window, g_pWindowManager->Screen->root, 30000, 30000); } } void CStatusBar::setup(int MonitorID) { Debug::log(LOG, "Creating the bar!"); if (MonitorID > g_pWindowManager->monitors.size()) { MonitorID = 0; Debug::log(ERR, "Incorrect value in MonitorID for the bar. Setting to 0."); } m_bHasTray = false; for (auto& mod : g_pWindowManager->statusBar->modules) { if (mod->value == "tray") { m_bHasTray = true; break; } } const auto MONITOR = g_pWindowManager->monitors[MonitorID]; Debug::log(LOG, "Bar monitor found to be " + std::to_string(MONITOR.ID)); m_iMonitorID = MonitorID; m_vecPosition = MONITOR.vecPosition; m_vecSize = Vector2D(MONITOR.vecSize.x, ConfigManager::getInt("bar:height")); uint32_t values[4]; // window m_iWindowID = (xcb_generate_id(g_pWindowManager->DisplayConnection)); // send the message IMMEDIATELY so that the main thread has time to update our WID. SIPCMessageBarToMain message; message.windowID = m_iWindowID; IPCSendMessage(g_pWindowManager->m_sIPCBarPipeOut.szPipeName, message); values[0] = ConfigManager::getInt("bar:col.bg"); values[1] = ConfigManager::getInt("bar:col.bg"); values[2] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE; values[3] = g_pWindowManager->Colormap; xcb_create_window(g_pWindowManager->DisplayConnection, g_pWindowManager->Depth, m_iWindowID, g_pWindowManager->Screen->root, m_vecPosition.x, m_vecPosition.y, m_vecSize.x, m_vecSize.y, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, g_pWindowManager->VisualType->visual_id, XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP, values); // Set the state to dock to avoid some issues xcb_atom_t dockAtom[] = { HYPRATOMS["_NET_WM_WINDOW_TYPE_DOCK"] }; xcb_ewmh_set_wm_window_type(g_pWindowManager->EWMHConnection, m_iWindowID, 1, dockAtom); // map xcb_map_window(g_pWindowManager->DisplayConnection, m_iWindowID); Debug::log(LOG, "Bar mapping!"); // Create a pixmap for writing to. m_iPixmap = xcb_generate_id(g_pWindowManager->DisplayConnection); xcb_create_pixmap(g_pWindowManager->DisplayConnection, g_pWindowManager->Depth, m_iPixmap, m_iWindowID, m_vecSize.x, m_vecSize.y); // setup contexts auto contextBG = &m_mContexts["BG"]; contextBG->GContext = xcb_generate_id(g_pWindowManager->DisplayConnection); values[0] = ConfigManager::getInt("bar:col.bg"); values[1] = ConfigManager::getInt("bar:col.bg"); xcb_create_gc(g_pWindowManager->DisplayConnection, contextBG->GContext, m_iPixmap, XCB_GC_BACKGROUND | XCB_GC_FOREGROUND, values); // // m_pCairoSurface = cairo_xcb_surface_create(g_pWindowManager->DisplayConnection, m_iPixmap, g_pWindowManager->VisualType, m_vecSize.x, m_vecSize.y); m_pCairo = cairo_create(m_pCairoSurface); cairo_surface_destroy(m_pCairoSurface); // fix tray fixTrayOnCreate(); Debug::log(LOG, "Bar setup done!"); m_bIsDestroyed = false; } void CStatusBar::destroy() { Debug::log(LOG, "Destroying the bar!"); if (m_bIsDestroyed) return; saveTrayOnDestroy(); xcb_close_font(g_pWindowManager->DisplayConnection, m_mContexts["HITEXT"].Font); xcb_unmap_window(g_pWindowManager->DisplayConnection, m_iWindowID); xcb_destroy_window(g_pWindowManager->DisplayConnection, m_iWindowID); xcb_destroy_window(g_pWindowManager->DisplayConnection, m_iPixmap); xcb_free_gc(g_pWindowManager->DisplayConnection, m_mContexts["BG"].GContext); xcb_free_gc(g_pWindowManager->DisplayConnection, m_mContexts["MEDBG"].GContext); xcb_free_gc(g_pWindowManager->DisplayConnection, m_mContexts["TEXT"].GContext); xcb_free_gc(g_pWindowManager->DisplayConnection, m_mContexts["HITEXT"].GContext); // Free cairo cairo_destroy(m_pCairo); m_pCairo = nullptr; m_bIsDestroyed = true; } int CStatusBar::getTextWidth(std::string text, std::string font) { cairo_select_font_face(m_pCairo, font.c_str(), CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(m_pCairo, 12); cairo_text_extents_t textextents; cairo_text_extents(m_pCairo, text.c_str(), &textextents); return textextents.width + 1 /* pad */; } void CStatusBar::drawText(Vector2D pos, std::string text, uint32_t color, std::string font) { cairo_select_font_face(m_pCairo, font.c_str(), CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(m_pCairo, 12); cairo_set_source_rgba(m_pCairo, RED(color), GREEN(color), BLUE(color), ALPHA(color)); cairo_move_to(m_pCairo, pos.x, pos.y); cairo_show_text(m_pCairo, text.c_str()); } void CStatusBar::drawCairoRectangle(Vector2D pos, Vector2D size, uint32_t color) { cairo_set_source_rgba(m_pCairo, RED(color), GREEN(color), BLUE(color), ALPHA(color)); cairo_rectangle(m_pCairo, pos.x, pos.y, size.x, size.y); cairo_fill(m_pCairo); } int CStatusBar::getTextHalfY() { return m_vecSize.y - (m_vecSize.y - 9) / 2.f; } void CStatusBar::drawErrorScreen() { drawCairoRectangle(Vector2D(0, 0), m_vecSize, 0xFFaa1111); drawText(Vector2D(1, getTextHalfY()), ConfigManager::parseError, 0xff000000, ConfigManager::getString("bar:font.main")); // do all the drawing cuz we return later cairo_surface_flush(m_pCairoSurface); xcb_copy_area(g_pWindowManager->DisplayConnection, m_iPixmap, m_iWindowID, m_mContexts["BG"].GContext, 0, 0, 0, 0, m_vecSize.x, m_vecSize.y); xcb_flush(g_pWindowManager->DisplayConnection); } void CStatusBar::draw() { if (m_bIsDestroyed) return; if (!m_pCairo) { Debug::log(ERR, "Cairo is null but attempted to draw!"); return; } // Clear the entire pixmap cairo_save(m_pCairo); cairo_set_operator(m_pCairo, CAIRO_OPERATOR_CLEAR); cairo_paint(m_pCairo); cairo_restore(m_pCairo); // if (ConfigManager::parseError != "") { // draw a special error screen drawErrorScreen(); return; } drawCairoRectangle(Vector2D(0, 0), m_vecSize, ConfigManager::getInt("bar:col.bg")); // // // DRAW ALL MODULES int offLeft = 0; int offRight = 0; for (auto& module : modules) { if (!module->bgcontext && !module->isPad) setupModule(module); if (module->value == "workspaces") { offLeft += drawWorkspacesModule(module, offLeft); } else { if (module->alignment == LEFT) { offLeft += drawModule(module, offLeft); } else if (module->alignment == RIGHT) { offRight += drawModule(module, offRight); } else { drawModule(module, 0); } } } // // // // fix the fucking tray if (!m_bHasTray) { uint32_t values[2]; for (auto& tray : g_pWindowManager->trayclients) { tray.hidden = true; values[0] = 30000; values[1] = 30000; xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); } } cairo_surface_flush(m_pCairoSurface); xcb_copy_area(g_pWindowManager->DisplayConnection, m_iPixmap, m_iWindowID, m_mContexts["BG"].GContext, 0, 0, 0, 0, m_vecSize.x, m_vecSize.y); xcb_flush(g_pWindowManager->DisplayConnection); } // Returns the width int CStatusBar::drawWorkspacesModule(SBarModule* mod, int off) { // Draw workspaces int drawnWorkspaces = 0; for (long unsigned int i = 0; i < openWorkspaces.size(); ++i) { const auto WORKSPACE = openWorkspaces[i]; // The LastWindow may be on a different one. This is where the mouse is. const auto MOUSEWORKSPACEID = m_iCurrentWorkspace; if (!WORKSPACE) continue; std::string workspaceName = std::to_string(openWorkspaces[i]); 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")); drawText(Vector2D(off + m_vecSize.y * drawnWorkspaces + m_vecSize.y / 2.f - getTextWidth(workspaceName, ConfigManager::getString("bar:font.main")) / 2.f, getTextHalfY()), workspaceName, WORKSPACE == MOUSEWORKSPACEID ? 0xFF111111 : 0xFFFFFFFF, ConfigManager::getString("bar:font.main")); drawnWorkspaces++; } return drawnWorkspaces * m_vecSize.y; } int CStatusBar::drawTrayModule(SBarModule* mod, int off) { const auto PAD = 2; const auto ELEMENTWIDTH = (m_vecSize.y - 2 < 1 ? 1 : m_vecSize.y - 2); const auto MODULEWIDTH = g_pWindowManager->trayclients.size() * (ELEMENTWIDTH + PAD); Vector2D position; switch (mod->alignment) { case LEFT: position = Vector2D(off, 0); break; case RIGHT: position = Vector2D(m_vecSize.x - off - MODULEWIDTH, 0); break; case CENTER: position = Vector2D(m_vecSize.x / 2.f - (MODULEWIDTH) / 2.f, 0); break; } // draw tray if (MODULEWIDTH < 1) return 0; drawCairoRectangle(position, Vector2D(MODULEWIDTH, m_vecSize.y), mod->bgcolor); int i = 0; for (auto& tray : g_pWindowManager->trayclients) { uint32_t values[] = {(int)(position.x + (i * (ELEMENTWIDTH + PAD)) + PAD / 2.f), (int)position.y + 1, (int)XCB_STACK_MODE_ABOVE}; if (tray.hidden) { values[0] = -999; values[1] = -999; xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_STACK_MODE, values); continue; } xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_STACK_MODE, values); // fix the size values[0] = ELEMENTWIDTH; values[1] = values[0]; xcb_configure_window(g_pWindowManager->DisplayConnection, tray.window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); ++i; } return MODULEWIDTH; } int CStatusBar::drawModule(SBarModule* mod, int off) { if (mod->isPad) return mod->pad; if (mod->value == "tray") return drawTrayModule(mod, off); const int PAD = ConfigManager::getInt("bar:mod_pad_in"); // check if we need to update if (std::chrono::duration_cast(std::chrono::system_clock::now() - mod->updateLast).count() > mod->updateEveryMs) { // This is done asynchronously to prevent lag on especially slower PCs // but ngl it also did hang on more powerful ones std::thread([=](){ const auto RETVAL = BarCommands::parseCommand(mod->value); mod->accessValueCalculated(true, RETVAL); mod->updateLast = std::chrono::system_clock::now(); }).detach(); } // We have the value, draw the module! const auto MODULEWIDTH = getTextWidth(mod->valueCalculated, ConfigManager::getString("bar:font.main")) + PAD; const auto ICONWIDTH = getTextWidth(mod->icon, ConfigManager::getString("bar:font.secondary")); if (!MODULEWIDTH || mod->accessValueCalculated(false) == "") return 0; // empty module Vector2D position; switch (mod->alignment) { case LEFT: position = Vector2D(off, 0); break; case RIGHT: position = Vector2D(m_vecSize.x - off - MODULEWIDTH - ICONWIDTH, 0); break; case CENTER: position = Vector2D(m_vecSize.x / 2.f - (MODULEWIDTH + ICONWIDTH) / 2.f, 0); break; } drawCairoRectangle(position, Vector2D(MODULEWIDTH + ICONWIDTH, m_vecSize.y), mod->bgcolor); drawText(position + Vector2D(PAD / 2, getTextHalfY()), mod->icon, mod->color, ConfigManager::getString("bar:font.secondary")); drawText(position + Vector2D(PAD / 2 + ICONWIDTH, getTextHalfY()), mod->accessValueCalculated(false), mod->color, ConfigManager::getString("bar:font.main")); return MODULEWIDTH + ICONWIDTH; } void CStatusBar::ensureTrayClientDead(xcb_window_t window) { auto temp = g_pWindowManager->trayclients; g_pWindowManager->trayclients.clear(); for (auto& trayitem : temp) { if (trayitem.window != window) g_pWindowManager->trayclients.push_back(trayitem); } Debug::log(LOG, "Ensured client dead (Bar, Tray)"); } void CStatusBar::ensureTrayClientHidden(xcb_window_t window, bool hide) { for (auto& trayitem : g_pWindowManager->trayclients) { if (trayitem.window == window) trayitem.hidden = hide; } }