mirror of
https://github.com/polybar/polybar.git
synced 2024-11-25 13:55:47 -05:00
refactor: Separate render component
This commit is contained in:
parent
c1162960cc
commit
25e33b6aab
16 changed files with 985 additions and 924 deletions
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "common.hpp"
|
#include "common.hpp"
|
||||||
#include "components/config.hpp"
|
#include "components/config.hpp"
|
||||||
#include "components/logger.hpp"
|
|
||||||
#include "components/types.hpp"
|
#include "components/types.hpp"
|
||||||
#include "utils/concurrency.hpp"
|
#include "utils/concurrency.hpp"
|
||||||
#include "utils/throttle.hpp"
|
#include "utils/throttle.hpp"
|
||||||
|
@ -15,11 +14,12 @@ POLYBAR_NS
|
||||||
|
|
||||||
// fwd
|
// fwd
|
||||||
class tray_manager;
|
class tray_manager;
|
||||||
class font_manager;
|
class logger;
|
||||||
|
class renderer;
|
||||||
|
|
||||||
class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::property_notify> {
|
class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::property_notify> {
|
||||||
public:
|
public:
|
||||||
explicit bar(connection& conn, const config& config, const logger& logger, unique_ptr<font_manager> font_manager,
|
explicit bar(connection& conn, const config& config, const logger& logger,
|
||||||
unique_ptr<tray_manager> tray_manager);
|
unique_ptr<tray_manager> tray_manager);
|
||||||
~bar();
|
~bar();
|
||||||
|
|
||||||
|
@ -32,14 +32,9 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert
|
||||||
void parse(string data, bool force = false);
|
void parse(string data, bool force = false);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void flush();
|
|
||||||
void refresh_window();
|
void refresh_window();
|
||||||
void load_fonts();
|
|
||||||
void configure_geom();
|
|
||||||
void create_monitor();
|
void create_monitor();
|
||||||
void create_window();
|
void configure_geom();
|
||||||
void create_pixmap();
|
|
||||||
void create_gcontexts();
|
|
||||||
void restack_window();
|
void restack_window();
|
||||||
void map_window();
|
void map_window();
|
||||||
void set_wmhints();
|
void set_wmhints();
|
||||||
|
@ -51,62 +46,35 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert
|
||||||
void handle(const evt::expose& evt);
|
void handle(const evt::expose& evt);
|
||||||
void handle(const evt::property_notify& evt);
|
void handle(const evt::property_notify& evt);
|
||||||
|
|
||||||
void on_alignment_change(alignment align);
|
void on_alignment_change(const alignment align);
|
||||||
void on_attribute_set(attribute attr);
|
void on_attribute_set(const attribute attr);
|
||||||
void on_attribute_unset(attribute attr);
|
void on_attribute_unset(const attribute attr);
|
||||||
void on_attribute_toggle(attribute attr);
|
void on_attribute_toggle(const attribute attr);
|
||||||
void on_action_block_open(mousebtn btn, string cmd);
|
void on_action_block_open(const mousebtn btn, string cmd);
|
||||||
void on_action_block_close(mousebtn btn);
|
void on_action_block_close(const mousebtn btn);
|
||||||
void on_color_change(gc gc_, color color_);
|
void on_color_change(const gc gc_, uint32_t color);
|
||||||
void on_font_change(int index);
|
void on_font_change(int index);
|
||||||
void on_pixel_offset(int px);
|
void on_pixel_offset(int px);
|
||||||
void on_tray_report(uint16_t slots);
|
void on_tray_report(uint16_t slots);
|
||||||
|
|
||||||
void draw_background();
|
|
||||||
void draw_border(border border_);
|
|
||||||
void draw_lines(int x, int w);
|
|
||||||
int draw_shift(int x, int chr_width);
|
|
||||||
void draw_character(uint16_t character);
|
|
||||||
void draw_textstring(const char* text, size_t len);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
connection& m_connection;
|
connection& m_connection;
|
||||||
const config& m_conf;
|
const config& m_conf;
|
||||||
const logger& m_log;
|
const logger& m_log;
|
||||||
unique_ptr<font_manager> m_fontmanager;
|
|
||||||
unique_ptr<tray_manager> m_tray;
|
unique_ptr<tray_manager> m_tray;
|
||||||
|
unique_ptr<renderer> m_renderer;
|
||||||
concurrency_util::spin_lock m_lock;
|
|
||||||
throttle_util::throttle_t m_throttler;
|
|
||||||
|
|
||||||
xcb_screen_t* m_screen;
|
|
||||||
rect m_screensize{};
|
|
||||||
|
|
||||||
xcb_visualtype_t* m_visual;
|
|
||||||
|
|
||||||
window m_window{m_connection, m_connection.generate_id()};
|
|
||||||
colormap m_colormap{m_connection, m_connection.generate_id()};
|
|
||||||
pixmap m_pixmap{m_connection, m_connection.generate_id()};
|
|
||||||
|
|
||||||
// xcb_gcontext_t m_root_gc{0};
|
|
||||||
// graphics_util::root_pixmap m_rootpixmap;
|
|
||||||
|
|
||||||
bar_settings m_opts;
|
bar_settings m_opts;
|
||||||
map<border, border_settings> m_borders;
|
xcb_window_t m_window;
|
||||||
map<gc, gcontext> m_gcontexts;
|
xcb_screen_t* m_screen;
|
||||||
vector<action_block> m_actions;
|
size m_screensize{};
|
||||||
|
bool m_sinkattached{false};
|
||||||
|
string m_lastinput;
|
||||||
|
|
||||||
stateflag m_sinkattached{false};
|
alignment m_trayalign{alignment::NONE};
|
||||||
|
uint8_t m_trayclients{0};
|
||||||
|
|
||||||
alignment m_traypos{alignment::NONE};
|
std::mutex m_mutex;
|
||||||
uint16_t m_trayclients{0};
|
|
||||||
|
|
||||||
string m_prevdata;
|
|
||||||
int m_xpos{0};
|
|
||||||
int m_attributes{0};
|
|
||||||
|
|
||||||
xcb_font_t m_gcfont{0};
|
|
||||||
XftDraw* m_xftdraw;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
di::injector<unique_ptr<bar>> configure_bar();
|
di::injector<unique_ptr<bar>> configure_bar();
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common.hpp"
|
#include "common.hpp"
|
||||||
#include "components/signals.hpp"
|
|
||||||
|
|
||||||
POLYBAR_NS
|
POLYBAR_NS
|
||||||
|
|
||||||
|
struct bar_settings;
|
||||||
|
enum class attribute : uint8_t;
|
||||||
|
enum class mousebtn : uint8_t;
|
||||||
|
|
||||||
DEFINE_ERROR(unrecognized_token);
|
DEFINE_ERROR(unrecognized_token);
|
||||||
|
|
||||||
class parser {
|
class parser {
|
||||||
public:
|
public:
|
||||||
explicit parser(const bar_settings& bar) : m_bar(bar) {}
|
explicit parser(const bar_settings& bar);
|
||||||
void operator()(string data);
|
void operator()(string data);
|
||||||
void codeblock(string data);
|
void codeblock(string data);
|
||||||
size_t text(string data);
|
size_t text(string data);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
color parse_color(string s, color fallback = color{0});
|
uint32_t parse_color(string s, uint32_t fallback = 0);
|
||||||
int parse_fontindex(string s);
|
int8_t parse_fontindex(string s);
|
||||||
attribute parse_attr(const char s);
|
attribute parse_attr(const char s);
|
||||||
mousebtn parse_action_btn(string data);
|
mousebtn parse_action_btn(string data);
|
||||||
string parse_action_cmd(string data);
|
string parse_action_cmd(string data);
|
||||||
|
|
83
include/components/renderer.hpp
Normal file
83
include/components/renderer.hpp
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.hpp"
|
||||||
|
#include "components/types.hpp"
|
||||||
|
#include "x11/types.hpp"
|
||||||
|
|
||||||
|
POLYBAR_NS
|
||||||
|
|
||||||
|
class connection;
|
||||||
|
class font_manager;
|
||||||
|
class logger;
|
||||||
|
|
||||||
|
class renderer {
|
||||||
|
public:
|
||||||
|
explicit renderer(connection& conn, const logger& logger, unique_ptr<font_manager> font_manager,
|
||||||
|
const bar_settings& bar, const vector<string>& fonts);
|
||||||
|
|
||||||
|
xcb_window_t window() const;
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
void end();
|
||||||
|
void redraw();
|
||||||
|
|
||||||
|
void reserve_space(edge side, uint16_t w);
|
||||||
|
|
||||||
|
void set_background(const gc gcontext, const uint32_t color);
|
||||||
|
void set_foreground(const gc gcontext, const uint32_t color);
|
||||||
|
void set_fontindex(const uint8_t font);
|
||||||
|
void set_alignment(const alignment align);
|
||||||
|
void set_attribute(const attribute attr, const bool state);
|
||||||
|
|
||||||
|
void fill_background();
|
||||||
|
void fill_border(const map<edge, border_settings>& borders, edge border);
|
||||||
|
void fill_overline(int16_t x, uint16_t w);
|
||||||
|
void fill_underline(int16_t x, uint16_t w);
|
||||||
|
|
||||||
|
void draw_character(uint16_t character);
|
||||||
|
void draw_textstring(const char* text, size_t len);
|
||||||
|
|
||||||
|
int16_t shift_content(int16_t x, int16_t shift_x);
|
||||||
|
int16_t shift_content(int16_t shift_x);
|
||||||
|
|
||||||
|
void begin_action(const mousebtn btn, const string& cmd);
|
||||||
|
void end_action(const mousebtn btn);
|
||||||
|
const vector<action_block> get_actions();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void debughints();
|
||||||
|
|
||||||
|
private:
|
||||||
|
connection& m_connection;
|
||||||
|
const logger& m_log;
|
||||||
|
unique_ptr<font_manager> m_fontmanager;
|
||||||
|
|
||||||
|
const bar_settings& m_bar;
|
||||||
|
|
||||||
|
xcb_window_t m_window;
|
||||||
|
xcb_colormap_t m_colormap;
|
||||||
|
xcb_visualtype_t* m_visual;
|
||||||
|
// xcb_gcontext_t m_gcontext;
|
||||||
|
xcb_pixmap_t m_pixmap;
|
||||||
|
|
||||||
|
map<gc, xcb_gcontext_t> m_gcontexts;
|
||||||
|
map<alignment, xcb_pixmap_t> m_pixmaps;
|
||||||
|
vector<action_block> m_actions;
|
||||||
|
|
||||||
|
// bool m_autosize{false};
|
||||||
|
int m_currentx{0};
|
||||||
|
int m_attributes{0};
|
||||||
|
alignment m_alignment{alignment::NONE};
|
||||||
|
|
||||||
|
xcb_font_t m_gcfont{0};
|
||||||
|
|
||||||
|
uint32_t m_background{0};
|
||||||
|
uint32_t m_foreground{0};
|
||||||
|
|
||||||
|
edge m_reserve_at{edge::NONE};
|
||||||
|
uint16_t m_reserve;
|
||||||
|
};
|
||||||
|
|
||||||
|
di::injector<unique_ptr<renderer>> configure_renderer(const bar_settings& bar, const vector<string>& fonts);
|
||||||
|
|
||||||
|
POLYBAR_NS_END
|
|
@ -8,12 +8,11 @@ POLYBAR_NS
|
||||||
|
|
||||||
// fwd decl {{{
|
// fwd decl {{{
|
||||||
|
|
||||||
enum class mousebtn;
|
enum class mousebtn : uint8_t;
|
||||||
enum class syntaxtag;
|
enum class syntaxtag : uint8_t;
|
||||||
enum class alignment;
|
enum class alignment : uint8_t;
|
||||||
enum class attribute;
|
enum class attribute : uint8_t;
|
||||||
enum class gc;
|
enum class gc : uint8_t;
|
||||||
class color;
|
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
@ -23,27 +22,26 @@ class color;
|
||||||
namespace g_signals {
|
namespace g_signals {
|
||||||
namespace bar {
|
namespace bar {
|
||||||
extern callback<string> action_click;
|
extern callback<string> action_click;
|
||||||
extern callback<bool> visibility_change;
|
extern callback<const bool> visibility_change;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace parser {
|
namespace parser {
|
||||||
extern callback<alignment> alignment_change;
|
extern callback<const alignment> alignment_change;
|
||||||
extern callback<attribute> attribute_set;
|
extern callback<const attribute> attribute_set;
|
||||||
extern callback<attribute> attribute_unset;
|
extern callback<const attribute> attribute_unset;
|
||||||
extern callback<attribute> attribute_toggle;
|
extern callback<const mousebtn, string> action_block_open;
|
||||||
extern callback<mousebtn, string> action_block_open;
|
extern callback<const mousebtn> action_block_close;
|
||||||
extern callback<mousebtn> action_block_close;
|
extern callback<const gc, const uint32_t> color_change;
|
||||||
extern callback<gc, color> color_change;
|
extern callback<const int8_t> font_change;
|
||||||
extern callback<int> font_change;
|
extern callback<const int16_t> pixel_offset;
|
||||||
extern callback<int> pixel_offset;
|
extern callback<const uint16_t> ascii_text_write;
|
||||||
extern callback<uint16_t> ascii_text_write;
|
extern callback<const uint16_t> unicode_text_write;
|
||||||
extern callback<uint16_t> unicode_text_write;
|
extern callback<const char*, const size_t> string_write;
|
||||||
extern callback<const char*, size_t> string_write;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace tray {
|
namespace tray {
|
||||||
extern callback<uint16_t> report_slotcount;
|
extern callback<const uint16_t> report_slotcount;
|
||||||
extern callback<uint32_t> clear_bg;
|
extern callback<const uint32_t> clear_bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,19 +6,13 @@
|
||||||
|
|
||||||
POLYBAR_NS
|
POLYBAR_NS
|
||||||
|
|
||||||
enum class border { NONE = 0, TOP, BOTTOM, LEFT, RIGHT, ALL };
|
enum class edge : uint8_t { NONE = 0, TOP, BOTTOM, LEFT, RIGHT, ALL };
|
||||||
enum class alignment { NONE = 0, LEFT, CENTER, RIGHT };
|
enum class alignment : uint8_t { NONE = 0, LEFT, CENTER, RIGHT };
|
||||||
enum class syntaxtag { NONE = 0, A, B, F, T, U, O, R, o, u };
|
enum class syntaxtag : uint8_t { NONE = 0, A, B, F, T, U, O, R, o, u };
|
||||||
enum class attribute { NONE = 0, o = 2, u = 4 };
|
enum class attribute : uint8_t { NONE = 0, o = 1 << 0, u = 1 << 1 };
|
||||||
enum class mousebtn { NONE = 0, LEFT, MIDDLE, RIGHT, SCROLL_UP, SCROLL_DOWN };
|
enum class mousebtn : uint8_t { NONE = 0, LEFT, MIDDLE, RIGHT, SCROLL_UP, SCROLL_DOWN };
|
||||||
enum class gc { NONE = 0, BG, FG, OL, UL, BT, BB, BL, BR };
|
enum class gc : uint8_t { NONE = 0, BG, FG, OL, UL, BT, BB, BL, BR };
|
||||||
|
enum class strut : uint16_t {
|
||||||
struct rect {
|
|
||||||
uint16_t w{0};
|
|
||||||
uint16_t h{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class strut {
|
|
||||||
LEFT = 0,
|
LEFT = 0,
|
||||||
RIGHT,
|
RIGHT,
|
||||||
TOP,
|
TOP,
|
||||||
|
@ -33,80 +27,73 @@ enum class strut {
|
||||||
BOTTOM_END_X,
|
BOTTOM_END_X,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct strut_margins {
|
struct position {
|
||||||
uint16_t t;
|
|
||||||
uint16_t b;
|
|
||||||
uint16_t l;
|
|
||||||
uint16_t r;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct bar_settings {
|
|
||||||
bar_settings() = default;
|
|
||||||
|
|
||||||
string locale;
|
|
||||||
|
|
||||||
int16_t x{0};
|
int16_t x{0};
|
||||||
int16_t y{0};
|
int16_t y{0};
|
||||||
uint16_t width{0};
|
};
|
||||||
uint16_t height{0};
|
|
||||||
|
|
||||||
int16_t offset_y{0};
|
struct size {
|
||||||
int16_t offset_x{0};
|
uint16_t w{0};
|
||||||
|
uint16_t h{0};
|
||||||
|
};
|
||||||
|
|
||||||
uint16_t padding_left{0};
|
struct side_values {
|
||||||
uint16_t padding_right{0};
|
uint16_t left{0};
|
||||||
|
uint16_t right{0};
|
||||||
|
};
|
||||||
|
|
||||||
int16_t module_margin_left{0};
|
struct edge_values {
|
||||||
int16_t module_margin_right{2};
|
uint16_t left{0};
|
||||||
|
uint16_t right{0};
|
||||||
int16_t lineheight{0};
|
uint16_t top{0};
|
||||||
int16_t spacing{1};
|
uint16_t bottom{0};
|
||||||
string separator;
|
|
||||||
|
|
||||||
color background{g_colorwhite};
|
|
||||||
color foreground{g_colorblack};
|
|
||||||
color linecolor{g_colorblack};
|
|
||||||
|
|
||||||
alignment align{alignment::RIGHT};
|
|
||||||
|
|
||||||
bool bottom{false};
|
|
||||||
bool dock{false};
|
|
||||||
|
|
||||||
monitor_t monitor;
|
|
||||||
string wmname;
|
|
||||||
|
|
||||||
int16_t vertical_mid{0};
|
|
||||||
|
|
||||||
strut_margins margins;
|
|
||||||
|
|
||||||
string geom() {
|
|
||||||
char buffer[32]{
|
|
||||||
'\0',
|
|
||||||
};
|
|
||||||
snprintf(buffer, sizeof(buffer), "%dx%d+%d+%d", width, height, x, y);
|
|
||||||
return string{*buffer};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct border_settings {
|
struct border_settings {
|
||||||
border_settings() = default;
|
uint32_t color{0xFF000000};
|
||||||
polybar::color color{g_colorblack};
|
|
||||||
uint16_t size{0};
|
uint16_t size{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct bar_settings {
|
||||||
|
monitor_t monitor;
|
||||||
|
|
||||||
|
edge origin{edge::BOTTOM};
|
||||||
|
|
||||||
|
size size{0, 0};
|
||||||
|
position pos{0, 0};
|
||||||
|
position offset{0, 0};
|
||||||
|
position center{0, 0};
|
||||||
|
side_values padding{0, 0};
|
||||||
|
side_values margin{0, 0};
|
||||||
|
side_values module_margin{0, 2};
|
||||||
|
edge_values strut{0, 0, 0, 0};
|
||||||
|
|
||||||
|
uint32_t background{0xFFFFFFFF};
|
||||||
|
uint32_t foreground{0xFF0000FF};
|
||||||
|
uint32_t linecolor{0xFF000000};
|
||||||
|
|
||||||
|
map<edge, border_settings> borders;
|
||||||
|
|
||||||
|
int8_t lineheight{0};
|
||||||
|
int8_t spacing{1};
|
||||||
|
string separator;
|
||||||
|
|
||||||
|
string wmname;
|
||||||
|
string locale;
|
||||||
|
|
||||||
|
bool force_docking{false};
|
||||||
|
};
|
||||||
|
|
||||||
struct action_block {
|
struct action_block {
|
||||||
action_block() = default;
|
alignment align{alignment::NONE};
|
||||||
mousebtn button{mousebtn::NONE};
|
|
||||||
string command;
|
|
||||||
int16_t start_x{0};
|
int16_t start_x{0};
|
||||||
int16_t end_x{0};
|
int16_t end_x{0};
|
||||||
alignment align;
|
mousebtn button{mousebtn::NONE};
|
||||||
|
string command;
|
||||||
bool active{true};
|
bool active{true};
|
||||||
#if DEBUG and DRAW_CLICKABLE_AREA_HINTS
|
#if DEBUG and DRAW_CLICKABLE_AREA_HINTS
|
||||||
xcb_window_t clickable_area;
|
xcb_window_t hint;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct wmsettings_bspwm {};
|
|
||||||
|
|
||||||
POLYBAR_NS_END
|
POLYBAR_NS_END
|
||||||
|
|
|
@ -54,7 +54,7 @@ namespace color_util {
|
||||||
return (a << 24) | (r << 16) | (g << 8) | b;
|
return (a << 24) | (r << 16) | (g << 8) | b;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T = uint8_t>
|
template <typename T>
|
||||||
string hex(uint32_t color) {
|
string hex(uint32_t color) {
|
||||||
char s[12];
|
char s[12];
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
@ -85,9 +85,9 @@ namespace color_util {
|
||||||
return hex;
|
return hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint32_t parse(string hex) {
|
inline uint32_t parse(string hex, uint32_t fallback = 0) {
|
||||||
if ((hex = parse_hex(hex)).empty())
|
if ((hex = parse_hex(hex)).empty())
|
||||||
return 0U;
|
return fallback;
|
||||||
return std::strtoul(&hex[1], nullptr, 16);
|
return std::strtoul(&hex[1], nullptr, 16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,36 +42,40 @@ class font_manager {
|
||||||
explicit font_manager(connection& conn, const logger& logger);
|
explicit font_manager(connection& conn, const logger& logger);
|
||||||
~font_manager();
|
~font_manager();
|
||||||
|
|
||||||
void set_preferred_font(int index);
|
bool load(string name, int8_t fontindex = -1, int8_t offset_y = 0);
|
||||||
|
|
||||||
bool load(string name, int fontindex = -1, int offset_y = 0);
|
void set_preferred_font(int8_t index);
|
||||||
|
|
||||||
font_t& match_char(uint16_t chr);
|
font_t& match_char(uint16_t chr);
|
||||||
|
uint8_t char_width(font_t& font, uint16_t chr);
|
||||||
int char_width(font_t& font, uint16_t chr);
|
|
||||||
|
|
||||||
XftColor xftcolor();
|
XftColor xftcolor();
|
||||||
|
XftDraw* xftdraw();
|
||||||
|
XftDraw* create_xftdraw(xcb_pixmap_t pm, xcb_colormap_t cm);
|
||||||
|
void destroy_xftdraw();
|
||||||
|
|
||||||
|
void allocate_color(uint32_t color, bool initial_alloc = false);
|
||||||
void allocate_color(XRenderColor color, bool initial_alloc = false);
|
void allocate_color(XRenderColor color, bool initial_alloc = false);
|
||||||
|
|
||||||
void set_gcontext_font(gcontext& gc, xcb_font_t font);
|
void set_gcontext_font(xcb_gcontext_t gc, xcb_font_t font);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool open_xcb_font(font_t& fontptr, string fontname);
|
bool open_xcb_font(font_t& fontptr, string fontname);
|
||||||
|
|
||||||
bool has_glyph(font_t& font, uint16_t chr);
|
bool has_glyph(font_t& font, uint16_t chr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
connection& m_connection;
|
connection& m_connection;
|
||||||
const logger& m_logger;
|
const logger& m_logger;
|
||||||
|
|
||||||
Display* m_display = nullptr;
|
Display* m_display{nullptr};
|
||||||
Visual* m_visual = nullptr;
|
Visual* m_visual{nullptr};
|
||||||
Colormap m_colormap;
|
Colormap m_colormap{};
|
||||||
|
|
||||||
map<int, font_t> m_fonts;
|
map<uint8_t, font_t> m_fonts;
|
||||||
int m_fontindex = -1;
|
int8_t m_fontindex{-1};
|
||||||
XftColor m_xftcolor;
|
|
||||||
|
XftColor m_xftcolor{};
|
||||||
|
XftDraw* m_xftdraw{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
di::injector<unique_ptr<font_manager>> configure_font_manager();
|
di::injector<unique_ptr<font_manager>> configure_font_manager();
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -248,7 +248,7 @@ void builder::font_close(bool force) {
|
||||||
void builder::background(string color) {
|
void builder::background(string color) {
|
||||||
if (color.length() == 2 || (color.find("#") == 0 && color.length() == 3)) {
|
if (color.length() == 2 || (color.find("#") == 0 && color.length() == 3)) {
|
||||||
color = "#" + color.substr(color.length() - 2);
|
color = "#" + color.substr(color.length() - 2);
|
||||||
auto bg = m_bar.background.source();
|
auto bg = color_util::hex<uint16_t>(m_bar.background);
|
||||||
color += bg.substr(bg.length() - (bg.length() < 6 ? 3 : 6));
|
color += bg.substr(bg.length() - (bg.length() < 6 ? 3 : 6));
|
||||||
} else if (color.length() >= 7 && color == "#" + string(color.length() - 1, color[1])) {
|
} else if (color.length() >= 7 && color == "#" + string(color.length() - 1, color[1])) {
|
||||||
color = color.substr(0, 4);
|
color = color.substr(0, 4);
|
||||||
|
@ -279,7 +279,7 @@ void builder::color(string color_) {
|
||||||
auto color(color_);
|
auto color(color_);
|
||||||
if (color.length() == 2 || (color.find("#") == 0 && color.length() == 3)) {
|
if (color.length() == 2 || (color.find("#") == 0 && color.length() == 3)) {
|
||||||
color = "#" + color.substr(color.length() - 2);
|
color = "#" + color.substr(color.length() - 2);
|
||||||
auto fg = m_bar.foreground.source();
|
auto fg = color_util::hex<uint16_t>(m_bar.foreground);
|
||||||
color += fg.substr(fg.length() - (fg.length() < 6 ? 3 : 6));
|
color += fg.substr(fg.length() - (fg.length() < 6 ? 3 : 6));
|
||||||
} else if (color.length() >= 7 && color == "#" + string(color.length() - 1, color[1])) {
|
} else if (color.length() >= 7 && color == "#" + string(color.length() - 1, color[1])) {
|
||||||
color = color.substr(0, 4);
|
color = color.substr(0, 4);
|
||||||
|
@ -299,7 +299,7 @@ void builder::color(string color_) {
|
||||||
|
|
||||||
void builder::color_alpha(string alpha_) {
|
void builder::color_alpha(string alpha_) {
|
||||||
auto alpha(alpha_);
|
auto alpha(alpha_);
|
||||||
string val = m_bar.foreground.source();
|
string val = color_util::hex<uint16_t>(m_bar.foreground);
|
||||||
if (alpha.find("#") == std::string::npos) {
|
if (alpha.find("#") == std::string::npos) {
|
||||||
alpha = "#" + alpha;
|
alpha = "#" + alpha;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "x11/color.hpp"
|
|
||||||
#include "components/bar.hpp"
|
#include "components/bar.hpp"
|
||||||
|
#include "components/config.hpp"
|
||||||
#include "components/controller.hpp"
|
#include "components/controller.hpp"
|
||||||
|
#include "components/eventloop.hpp"
|
||||||
|
#include "components/ipc.hpp"
|
||||||
|
#include "components/logger.hpp"
|
||||||
|
#include "components/signals.hpp"
|
||||||
#include "modules/backlight.hpp"
|
#include "modules/backlight.hpp"
|
||||||
#include "modules/battery.hpp"
|
#include "modules/battery.hpp"
|
||||||
#include "modules/bspwm.hpp"
|
#include "modules/bspwm.hpp"
|
||||||
|
@ -22,12 +24,6 @@
|
||||||
#include "modules/text.hpp"
|
#include "modules/text.hpp"
|
||||||
#include "modules/xbacklight.hpp"
|
#include "modules/xbacklight.hpp"
|
||||||
#include "modules/xwindow.hpp"
|
#include "modules/xwindow.hpp"
|
||||||
|
|
||||||
#include "components/config.hpp"
|
|
||||||
#include "components/eventloop.hpp"
|
|
||||||
#include "components/ipc.hpp"
|
|
||||||
#include "components/logger.hpp"
|
|
||||||
#include "components/signals.hpp"
|
|
||||||
#include "utils/process.hpp"
|
#include "utils/process.hpp"
|
||||||
#include "utils/string.hpp"
|
#include "utils/string.hpp"
|
||||||
|
|
||||||
|
@ -44,7 +40,7 @@
|
||||||
#include "modules/volume.hpp"
|
#include "modules/volume.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if not (ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA)
|
#if not(ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA)
|
||||||
#include "modules/unsupported.hpp"
|
#include "modules/unsupported.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -499,14 +495,16 @@ void controller::on_unrecognized_action(string input) {
|
||||||
* Callback for module content update
|
* Callback for module content update
|
||||||
*/
|
*/
|
||||||
void controller::on_update() {
|
void controller::on_update() {
|
||||||
|
const bar_settings& bar{m_bar->settings()};
|
||||||
|
|
||||||
string contents{""};
|
string contents{""};
|
||||||
string separator{m_bar->settings().separator};
|
string separator{bar.separator};
|
||||||
|
|
||||||
string padding_left(m_bar->settings().padding_left, ' ');
|
string padding_left(bar.padding.left, ' ');
|
||||||
string padding_right(m_bar->settings().padding_right, ' ');
|
string padding_right(bar.padding.right, ' ');
|
||||||
|
|
||||||
auto margin_left = m_bar->settings().module_margin_left;
|
auto margin_left = bar.module_margin.left;
|
||||||
auto margin_right = m_bar->settings().module_margin_right;
|
auto margin_right = bar.module_margin.right;
|
||||||
|
|
||||||
for (const auto& block : m_eventloop->modules()) {
|
for (const auto& block : m_eventloop->modules()) {
|
||||||
string block_contents;
|
string block_contents;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
#include "x11/color.hpp"
|
|
||||||
#include "components/types.hpp"
|
|
||||||
|
|
||||||
#include "components/parser.hpp"
|
#include "components/parser.hpp"
|
||||||
|
#include "components/signals.hpp"
|
||||||
|
#include "components/types.hpp"
|
||||||
#include "utils/math.hpp"
|
#include "utils/math.hpp"
|
||||||
#include "utils/string.hpp"
|
#include "utils/string.hpp"
|
||||||
|
|
||||||
POLYBAR_NS
|
POLYBAR_NS
|
||||||
|
|
||||||
|
parser::parser(const bar_settings& bar) : m_bar(bar) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse input data
|
* Parse input data
|
||||||
*/
|
*/
|
||||||
|
@ -111,11 +112,6 @@ void parser::codeblock(string data) {
|
||||||
g_signals::parser::attribute_unset(parse_attr(value[0]));
|
g_signals::parser::attribute_unset(parse_attr(value[0]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '!':
|
|
||||||
if (g_signals::parser::attribute_toggle)
|
|
||||||
g_signals::parser::attribute_toggle(parse_attr(value[0]));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'A':
|
case 'A':
|
||||||
if (isdigit(data[0]) || data[0] == ':') {
|
if (isdigit(data[0]) || data[0] == ':') {
|
||||||
value = parse_action_cmd(data);
|
value = parse_action_cmd(data);
|
||||||
|
@ -172,8 +168,7 @@ size_t parser::text(string data) {
|
||||||
return 2;
|
return 2;
|
||||||
} else if ((utf[0] & 0xf0) == 0xe0) { // 3 byte utf-8 sequence
|
} else if ((utf[0] & 0xf0) == 0xe0) { // 3 byte utf-8 sequence
|
||||||
if (g_signals::parser::unicode_text_write)
|
if (g_signals::parser::unicode_text_write)
|
||||||
g_signals::parser::unicode_text_write(
|
g_signals::parser::unicode_text_write((utf[0] & 0xf) << 12 | (utf[1] & 0x3f) << 6 | (utf[2] & 0x3f));
|
||||||
(utf[0] & 0xf) << 12 | (utf[1] & 0x3f) << 6 | (utf[2] & 0x3f));
|
|
||||||
return 3;
|
return 3;
|
||||||
} else if ((utf[0] & 0xf8) == 0xf0) { // 4 byte utf-8 sequence
|
} else if ((utf[0] & 0xf8) == 0xf0) { // 4 byte utf-8 sequence
|
||||||
if (g_signals::parser::unicode_text_write)
|
if (g_signals::parser::unicode_text_write)
|
||||||
|
@ -197,20 +192,26 @@ size_t parser::text(string data) {
|
||||||
/**
|
/**
|
||||||
* TODO: docstring
|
* TODO: docstring
|
||||||
*/
|
*/
|
||||||
color parser::parse_color(string s, color fallback) {
|
uint32_t parser::parse_color(string s, uint32_t fallback) {
|
||||||
if (s.empty() || s == "-")
|
uint32_t color{0};
|
||||||
|
if (s.empty() || s[0] == '-' || (color = color_util::parse(s, fallback)) == fallback)
|
||||||
return fallback;
|
return fallback;
|
||||||
return color::parse(s, fallback);
|
return color_util::premultiply_alpha(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: docstring
|
* TODO: docstring
|
||||||
*/
|
*/
|
||||||
int parser::parse_fontindex(string s) {
|
int8_t parser::parse_fontindex(string s) {
|
||||||
if (s.empty() || s == "-")
|
if (s.empty() || s == "-") {
|
||||||
return -1;
|
return -1;
|
||||||
char* p = (char*)s.c_str();
|
}
|
||||||
return std::strtoul(p, &p, 10);
|
|
||||||
|
try {
|
||||||
|
return std::stoul(s.c_str(), nullptr, 10);
|
||||||
|
} catch (const std::invalid_argument& err) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -246,8 +247,11 @@ mousebtn parser::parse_action_btn(string data) {
|
||||||
* TODO: docstring
|
* TODO: docstring
|
||||||
*/
|
*/
|
||||||
string parser::parse_action_cmd(string data) {
|
string parser::parse_action_cmd(string data) {
|
||||||
auto start = string_util::find_nth(data, 0, ":", 1);
|
size_t start, end;
|
||||||
auto end = string_util::find_nth(data, 0, ":", 2);
|
if ((start = data.find(':')) == string::npos)
|
||||||
|
return "";
|
||||||
|
if ((end = data.find(':', start + 1)) == string::npos)
|
||||||
|
return "";
|
||||||
return string_util::trim(data.substr(start, end), ':');
|
return string_util::trim(data.substr(start, end), ':');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
499
src/components/renderer.cpp
Normal file
499
src/components/renderer.cpp
Normal file
|
@ -0,0 +1,499 @@
|
||||||
|
#include "components/renderer.hpp"
|
||||||
|
#include "components/logger.hpp"
|
||||||
|
#include "x11/connection.hpp"
|
||||||
|
#include "x11/draw.hpp"
|
||||||
|
#include "x11/fonts.hpp"
|
||||||
|
#include "x11/winspec.hpp"
|
||||||
|
|
||||||
|
POLYBAR_NS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure injection module
|
||||||
|
*/
|
||||||
|
di::injector<unique_ptr<renderer>> configure_renderer(const bar_settings& bar, const vector<string>& fonts) {
|
||||||
|
// clang-format off
|
||||||
|
return di::make_injector(
|
||||||
|
di::bind<>().to(bar),
|
||||||
|
di::bind<>().to(fonts),
|
||||||
|
configure_connection(),
|
||||||
|
configure_logger(),
|
||||||
|
configure_font_manager());
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer::renderer(connection& conn, const logger& logger, unique_ptr<font_manager> font_manager,
|
||||||
|
const bar_settings& bar, const vector<string>& fonts)
|
||||||
|
: m_connection(conn), m_log(logger), m_fontmanager(forward<decltype(font_manager)>(font_manager)), m_bar(bar) {
|
||||||
|
auto screen = m_connection.screen();
|
||||||
|
|
||||||
|
m_log.trace("bar: Get true color visual");
|
||||||
|
m_visual = m_connection.visual_type(screen, 32).get();
|
||||||
|
|
||||||
|
m_log.trace("bar: Create colormap");
|
||||||
|
m_colormap = m_connection.generate_id();
|
||||||
|
m_connection.create_colormap(XCB_COLORMAP_ALLOC_NONE, m_colormap, screen->root, m_visual->visual_id);
|
||||||
|
|
||||||
|
m_window = m_connection.generate_id();
|
||||||
|
m_log.trace("bar: Create window %s", m_connection.id(m_window));
|
||||||
|
{
|
||||||
|
uint32_t mask{0};
|
||||||
|
uint32_t values[16]{0};
|
||||||
|
xcb_params_cw_t params;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
XCB_AUX_ADD_PARAM(&mask, ¶ms, back_pixel, 0);
|
||||||
|
XCB_AUX_ADD_PARAM(&mask, ¶ms, border_pixel, 0);
|
||||||
|
XCB_AUX_ADD_PARAM(&mask, ¶ms, backing_store, XCB_BACKING_STORE_WHEN_MAPPED);
|
||||||
|
XCB_AUX_ADD_PARAM(&mask, ¶ms, colormap, m_colormap);
|
||||||
|
XCB_AUX_ADD_PARAM(&mask, ¶ms, override_redirect, m_bar.force_docking);
|
||||||
|
XCB_AUX_ADD_PARAM(&mask, ¶ms, event_mask, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
xutils::pack_values(mask, ¶ms, values);
|
||||||
|
m_connection.create_window(32, m_window, screen->root, m_bar.pos.x, m_bar.pos.y, m_bar.size.w, m_bar.size.h, 0,
|
||||||
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, m_visual->visual_id, mask, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pixmap = m_connection.generate_id();
|
||||||
|
m_log.trace("bar: Create pixmap (xid=%s)", m_connection.id(m_pixmap));
|
||||||
|
m_connection.create_pixmap(32, m_pixmap, m_window, m_bar.size.w, m_bar.size.h);
|
||||||
|
|
||||||
|
m_log.trace("bar: Create gcontexts");
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
vector<uint32_t> colors {
|
||||||
|
m_bar.background,
|
||||||
|
m_bar.foreground,
|
||||||
|
m_bar.linecolor,
|
||||||
|
m_bar.linecolor,
|
||||||
|
m_bar.borders.at(edge::TOP).color,
|
||||||
|
m_bar.borders.at(edge::BOTTOM).color,
|
||||||
|
m_bar.borders.at(edge::LEFT).color,
|
||||||
|
m_bar.borders.at(edge::RIGHT).color,
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
for (int i = 1; i <= 8; i++) {
|
||||||
|
uint32_t mask{0};
|
||||||
|
uint32_t value_list[32]{0};
|
||||||
|
|
||||||
|
xcb_params_gc_t params;
|
||||||
|
XCB_AUX_ADD_PARAM(&mask, ¶ms, foreground, colors[i - 1]);
|
||||||
|
XCB_AUX_ADD_PARAM(&mask, ¶ms, graphics_exposures, 0);
|
||||||
|
|
||||||
|
xutils::pack_values(mask, ¶ms, value_list);
|
||||||
|
m_gcontexts.emplace(gc(i), m_connection.generate_id());
|
||||||
|
|
||||||
|
m_log.trace("bar: Create gcontext (gc=%i, xid=%s)", i, m_connection.id(m_gcontexts.at(gc(i))));
|
||||||
|
m_connection.create_gc(m_gcontexts.at(gc(i)), m_pixmap, mask, value_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_log.trace("bar: Load fonts");
|
||||||
|
{
|
||||||
|
auto fonts_loaded = false;
|
||||||
|
auto fontindex = 0;
|
||||||
|
|
||||||
|
if (fonts.empty())
|
||||||
|
m_log.warn("No fonts specified, using fallback font \"fixed\"");
|
||||||
|
|
||||||
|
for (auto f : fonts) {
|
||||||
|
fontindex++;
|
||||||
|
vector<string> fd = string_util::split(f, ';');
|
||||||
|
string pattern{fd[0]};
|
||||||
|
int offset{0};
|
||||||
|
|
||||||
|
if (fd.size() > 1)
|
||||||
|
offset = std::stoi(fd[1], 0, 10);
|
||||||
|
|
||||||
|
if (m_fontmanager->load(pattern, fontindex, offset))
|
||||||
|
fonts_loaded = true;
|
||||||
|
else
|
||||||
|
m_log.warn("Unable to load font '%s'", fd[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fonts_loaded && !fonts.empty())
|
||||||
|
m_log.warn("Unable to load fonts, using fallback font \"fixed\"");
|
||||||
|
|
||||||
|
if (!fonts_loaded && !m_fontmanager->load("fixed"))
|
||||||
|
throw application_error("Unable to load fonts");
|
||||||
|
|
||||||
|
m_fontmanager->allocate_color(m_bar.foreground, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_window_t renderer::window() const {
|
||||||
|
return m_window;
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::begin() {
|
||||||
|
#if DEBUG and DRAW_CLICKABLE_AREA_HINTS
|
||||||
|
for (auto&& action : m_actions) {
|
||||||
|
m_connection.destroy_window(action.hint);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_currentx = m_bar.borders.at(edge::LEFT).size;
|
||||||
|
m_attributes = 0;
|
||||||
|
m_actions.clear();
|
||||||
|
|
||||||
|
fill_background();
|
||||||
|
|
||||||
|
m_fontmanager->create_xftdraw(m_pixmap, m_colormap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::end() {
|
||||||
|
xcb_rectangle_t rect{0, 0, m_bar.size.w, m_bar.size.h};
|
||||||
|
|
||||||
|
if (m_reserve_at == edge::LEFT) {
|
||||||
|
rect.x += m_reserve;
|
||||||
|
rect.width -= m_reserve;
|
||||||
|
} else if (m_reserve_at == edge::RIGHT) {
|
||||||
|
rect.width -= m_reserve;
|
||||||
|
}
|
||||||
|
|
||||||
|
fill_border(m_bar.borders, edge::ALL);
|
||||||
|
|
||||||
|
m_connection.copy_area(
|
||||||
|
m_pixmap, m_window, m_gcontexts.at(gc::FG), rect.x, rect.y, rect.x, rect.y, rect.width, rect.height);
|
||||||
|
m_connection.flush();
|
||||||
|
|
||||||
|
m_fontmanager->destroy_xftdraw();
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
debughints();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_reserve = 0;
|
||||||
|
m_reserve_at = edge::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::redraw() {}
|
||||||
|
|
||||||
|
void renderer::reserve_space(edge side, uint16_t w) {
|
||||||
|
m_log.trace_x("renderer: reserve_space(%i, %i)", static_cast<uint8_t>(side), w);
|
||||||
|
m_reserve = w;
|
||||||
|
m_reserve_at = side;
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::set_background(const gc gcontext, const uint32_t color) {
|
||||||
|
if (color == m_background)
|
||||||
|
return;
|
||||||
|
m_log.trace_x("renderer: set_background(%i, #%08x)", static_cast<uint8_t>(gcontext), color);
|
||||||
|
m_connection.change_gc(m_gcontexts.at(gcontext), XCB_GC_BACKGROUND, &color);
|
||||||
|
m_background = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::set_foreground(const gc gcontext, const uint32_t color) {
|
||||||
|
if (color == m_foreground)
|
||||||
|
return;
|
||||||
|
m_log.trace_x("renderer: set_foreground(%i, #%08x)", static_cast<uint8_t>(gcontext), color);
|
||||||
|
m_connection.change_gc(m_gcontexts.at(gcontext), XCB_GC_FOREGROUND, &color);
|
||||||
|
if (gcontext == gc::FG)
|
||||||
|
m_fontmanager->allocate_color(color);
|
||||||
|
else if (gcontext == gc::BG)
|
||||||
|
shift_content(0);
|
||||||
|
m_foreground = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::set_fontindex(const uint8_t font) {
|
||||||
|
m_log.trace_x("renderer: set_fontindex(%i)", static_cast<uint8_t>(font));
|
||||||
|
m_fontmanager->set_preferred_font(font);
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::set_alignment(const alignment align) {
|
||||||
|
if (align == m_alignment) {
|
||||||
|
return;
|
||||||
|
} else if (align == alignment::LEFT) {
|
||||||
|
m_currentx = m_bar.borders.at(edge::LEFT).size;
|
||||||
|
} else if (align == alignment::RIGHT) {
|
||||||
|
m_currentx = m_bar.borders.at(edge::RIGHT).size;
|
||||||
|
} else {
|
||||||
|
m_currentx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (align == alignment::LEFT && m_reserve_at == edge::LEFT) {
|
||||||
|
m_currentx += m_reserve;
|
||||||
|
} else if (align == alignment::RIGHT && m_reserve_at == edge::RIGHT) {
|
||||||
|
m_currentx += m_reserve;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_log.trace_x("renderer: set_alignment(%i)", static_cast<uint8_t>(align));
|
||||||
|
m_alignment = align;
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::set_attribute(const attribute attr, bool state) {
|
||||||
|
m_log.trace_x("renderer: set_attribute(%i, %i)", static_cast<uint8_t>(attr), state);
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
m_attributes |= static_cast<uint8_t>(attr);
|
||||||
|
} else {
|
||||||
|
m_attributes ^= static_cast<uint8_t>(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::fill_background() {
|
||||||
|
xcb_rectangle_t rect{0, 0, m_bar.size.w, m_bar.size.h};
|
||||||
|
|
||||||
|
if (m_reserve_at == edge::LEFT) {
|
||||||
|
rect.x += m_reserve;
|
||||||
|
rect.width -= m_reserve;
|
||||||
|
} else if (m_reserve_at == edge::RIGHT) {
|
||||||
|
rect.width -= m_reserve;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BG), rect.x, rect.y, rect.width, rect.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::fill_border(const map<edge, border_settings>& borders, edge border) {
|
||||||
|
for (auto&& b : borders) {
|
||||||
|
if (border != edge::ALL && b.first != border)
|
||||||
|
continue;
|
||||||
|
if (b.second.size <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (b.first) {
|
||||||
|
case edge::TOP:
|
||||||
|
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BT), borders.at(edge::LEFT).size, 0,
|
||||||
|
m_bar.size.w - borders.at(edge::LEFT).size - borders.at(edge::RIGHT).size, borders.at(edge::TOP).size);
|
||||||
|
break;
|
||||||
|
case edge::BOTTOM:
|
||||||
|
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BB), borders.at(edge::LEFT).size,
|
||||||
|
m_bar.size.h - borders.at(edge::BOTTOM).size,
|
||||||
|
m_bar.size.w - borders.at(edge::LEFT).size - borders.at(edge::RIGHT).size, borders.at(edge::BOTTOM).size);
|
||||||
|
break;
|
||||||
|
case edge::LEFT:
|
||||||
|
draw_util::fill(
|
||||||
|
m_connection, m_pixmap, m_gcontexts.at(gc::BL), 0, 0, borders.at(edge::LEFT).size, m_bar.size.h);
|
||||||
|
break;
|
||||||
|
case edge::RIGHT:
|
||||||
|
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BR), m_bar.size.w - borders.at(edge::RIGHT).size, 0,
|
||||||
|
borders.at(edge::RIGHT).size, m_bar.size.h);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::fill_overline(int16_t x, uint16_t w) {
|
||||||
|
if (!m_bar.lineheight || !(m_attributes & static_cast<int>(attribute::o)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
draw_util::fill(
|
||||||
|
m_connection, m_pixmap, m_gcontexts.at(gc::OL), x, m_bar.borders.at(edge::TOP).size, w, m_bar.lineheight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::fill_underline(int16_t x, uint16_t w) {
|
||||||
|
if (!m_bar.lineheight || !(m_attributes & static_cast<int>(attribute::u)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::UL), x,
|
||||||
|
m_bar.size.h - m_bar.borders.at(edge::BOTTOM).size - m_bar.lineheight, w, m_bar.lineheight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::draw_character(uint16_t character) {
|
||||||
|
auto& font = m_fontmanager->match_char(character);
|
||||||
|
|
||||||
|
if (!font) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font->ptr && font->ptr != m_gcfont) {
|
||||||
|
m_gcfont = font->ptr;
|
||||||
|
m_fontmanager->set_gcontext_font(m_gcontexts.at(gc::FG), m_gcfont);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto width = m_fontmanager->char_width(font, character);
|
||||||
|
|
||||||
|
// Avoid odd glyph width's for center-aligned text
|
||||||
|
// since it breaks the positioning of clickable area's
|
||||||
|
if (m_alignment == alignment::CENTER && width % 2)
|
||||||
|
width++;
|
||||||
|
|
||||||
|
auto x = shift_content(width);
|
||||||
|
auto y = m_bar.center.y + font->height / 2 - font->descent + font->offset_y;
|
||||||
|
|
||||||
|
if (font->xft != nullptr) {
|
||||||
|
auto color = m_fontmanager->xftcolor();
|
||||||
|
XftDrawString16(m_fontmanager->xftdraw(), &color, font->xft, x, y, &character, 1);
|
||||||
|
} else {
|
||||||
|
uint16_t ucs = ((character >> 8) | (character << 8));
|
||||||
|
draw_util::xcb_poly_text_16_patched(m_connection, m_pixmap, m_gcontexts.at(gc::FG), x, y, 1, &ucs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::draw_textstring(const char* text, size_t len) {
|
||||||
|
for (size_t n = 0; n < len; n++) {
|
||||||
|
vector<uint16_t> chars;
|
||||||
|
chars.emplace_back(text[n]);
|
||||||
|
|
||||||
|
auto& font = m_fontmanager->match_char(chars[0]);
|
||||||
|
|
||||||
|
if (!font) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font->ptr && font->ptr != m_gcfont) {
|
||||||
|
m_gcfont = font->ptr;
|
||||||
|
m_fontmanager->set_gcontext_font(m_gcontexts.at(gc::FG), m_gcfont);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (n + 1 < len && text[n + 1] == chars[0]) {
|
||||||
|
chars.emplace_back(text[++n]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: cache
|
||||||
|
auto width = m_fontmanager->char_width(font, chars[0]) * chars.size();
|
||||||
|
|
||||||
|
// Avoid odd glyph width's for center-aligned text
|
||||||
|
// since it breaks the positioning of clickable area's
|
||||||
|
if (m_alignment == alignment::CENTER && width % 2)
|
||||||
|
width++;
|
||||||
|
|
||||||
|
auto x = shift_content(width);
|
||||||
|
auto y = m_bar.center.y + font->height / 2 - font->descent + font->offset_y;
|
||||||
|
|
||||||
|
if (font->xft != nullptr) {
|
||||||
|
auto color = m_fontmanager->xftcolor();
|
||||||
|
const FcChar16* drawchars = static_cast<const FcChar16*>(chars.data());
|
||||||
|
XftDrawString16(m_fontmanager->xftdraw(), &color, font->xft, x, y, drawchars, chars.size());
|
||||||
|
} else {
|
||||||
|
for (size_t i = 0; i < chars.size(); i++) {
|
||||||
|
chars[i] = ((chars[i] >> 8) | (chars[i] << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_util::xcb_poly_text_16_patched(
|
||||||
|
m_connection, m_pixmap, m_gcontexts.at(gc::FG), x, y, chars.size(), chars.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t renderer::shift_content(int16_t x, int16_t shift_x) {
|
||||||
|
int delta = shift_x;
|
||||||
|
|
||||||
|
if (m_alignment == alignment::CENTER) {
|
||||||
|
int base_x = m_bar.size.w;
|
||||||
|
base_x -= m_bar.borders.at(edge::RIGHT).size;
|
||||||
|
base_x /= 2;
|
||||||
|
base_x += m_bar.borders.at(edge::LEFT).size;
|
||||||
|
m_connection.copy_area(
|
||||||
|
m_pixmap, m_pixmap, m_gcontexts.at(gc::FG), base_x - x / 2, 0, base_x - (x + shift_x) / 2, 0, x, m_bar.size.h);
|
||||||
|
x = base_x - (x + shift_x) / 2 + x;
|
||||||
|
delta /= 2;
|
||||||
|
} else if (m_alignment == alignment::RIGHT) {
|
||||||
|
m_connection.copy_area(m_pixmap, m_pixmap, m_gcontexts.at(gc::FG), m_bar.size.w - x, 0, m_bar.size.w - x - shift_x,
|
||||||
|
0, x, m_bar.size.h);
|
||||||
|
x = m_bar.size.w - shift_x - m_bar.borders.at(edge::RIGHT).size;
|
||||||
|
if (m_reserve_at == edge::RIGHT)
|
||||||
|
x -= m_reserve;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BG), x, 0, m_bar.size.w - x, m_bar.size.h);
|
||||||
|
|
||||||
|
// Translate pos of clickable areas
|
||||||
|
if (m_alignment != alignment::LEFT) {
|
||||||
|
for (auto&& action : m_actions) {
|
||||||
|
if (action.active || action.align != m_alignment)
|
||||||
|
continue;
|
||||||
|
action.start_x -= delta;
|
||||||
|
action.end_x -= delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentx += shift_x;
|
||||||
|
|
||||||
|
fill_underline(x, shift_x);
|
||||||
|
fill_overline(x, shift_x);
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t renderer::shift_content(int16_t shift_x) {
|
||||||
|
return shift_content(m_currentx, shift_x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::begin_action(const mousebtn btn, const string& cmd) {
|
||||||
|
action_block action{};
|
||||||
|
action.button = btn;
|
||||||
|
action.align = m_alignment;
|
||||||
|
action.start_x = m_currentx;
|
||||||
|
action.command = string_util::replace_all(cmd, ":", "\\:");
|
||||||
|
action.active = true;
|
||||||
|
if (action.button == mousebtn::NONE)
|
||||||
|
action.button = mousebtn::LEFT;
|
||||||
|
m_log.trace_x("renderer: begin_action(%i, %s)", static_cast<uint8_t>(action.button), cmd.c_str());
|
||||||
|
m_actions.emplace_back(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::end_action(const mousebtn btn) {
|
||||||
|
for (auto action = m_actions.rbegin(); action != m_actions.rend(); action++) {
|
||||||
|
if (!action->active || action->button != btn)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
m_log.trace_x("renderer: end_action(%i, %s)", static_cast<uint8_t>(btn), action->command.c_str());
|
||||||
|
|
||||||
|
action->active = false;
|
||||||
|
|
||||||
|
if (action->align == alignment::LEFT) {
|
||||||
|
action->end_x = m_currentx;
|
||||||
|
} else if (action->align == alignment::CENTER) {
|
||||||
|
int base_x{m_bar.size.w};
|
||||||
|
int clickable_width{m_currentx - action->start_x};
|
||||||
|
base_x -= m_bar.borders.at(edge::RIGHT).size;
|
||||||
|
base_x /= 2;
|
||||||
|
base_x += m_bar.borders.at(edge::LEFT).size;
|
||||||
|
action->start_x = base_x - clickable_width / 2 + action->start_x / 2;
|
||||||
|
action->end_x = action->start_x + clickable_width;
|
||||||
|
} else if (action->align == alignment::RIGHT) {
|
||||||
|
int base_x{m_bar.size.w - m_bar.borders.at(edge::RIGHT).size};
|
||||||
|
if (m_reserve_at == edge::RIGHT)
|
||||||
|
base_x -= m_reserve;
|
||||||
|
action->start_x = base_x - m_currentx + action->start_x;
|
||||||
|
action->end_x = base_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vector<action_block> renderer::get_actions() {
|
||||||
|
return m_actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderer::debughints() {
|
||||||
|
#if DEBUG and DRAW_CLICKABLE_AREA_HINTS
|
||||||
|
map<alignment, int> hint_num{{
|
||||||
|
{alignment::LEFT, 0}, {alignment::CENTER, 0}, {alignment::RIGHT, 0},
|
||||||
|
}};
|
||||||
|
|
||||||
|
for (auto&& action : m_actions) {
|
||||||
|
if (action.active) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_log.info("Drawing clickable area hints");
|
||||||
|
|
||||||
|
hint_num[action.align]++;
|
||||||
|
|
||||||
|
auto x = action.start_x;
|
||||||
|
auto y = m_bar.y + hint_num[action.align]++ * DRAW_CLICKABLE_AREA_HINTS_OFFSET_Y;
|
||||||
|
auto w = action.end_x - action.start_x - 2;
|
||||||
|
auto h = m_bar.size.h - 2;
|
||||||
|
|
||||||
|
const uint32_t mask = XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT;
|
||||||
|
const uint32_t border_color = hint_num[action.align] % 2 ? 0xff0000 : 0x00ff00;
|
||||||
|
const uint32_t values[2]{border_color, true};
|
||||||
|
|
||||||
|
action.hint = m_connection.generate_id();
|
||||||
|
m_connection.create_window(m_screen->root_depth, action.hint, m_screen->root, x, y, w, h, 1,
|
||||||
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, m_screen->root_visual, mask, values);
|
||||||
|
m_connection.map_window(action.hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connection.flush();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
POLYBAR_NS_END
|
|
@ -12,18 +12,17 @@ callback<bool> g_signals::bar::visibility_change{nullptr};
|
||||||
/**
|
/**
|
||||||
* Signals used to communicate with the input parser
|
* Signals used to communicate with the input parser
|
||||||
*/
|
*/
|
||||||
callback<alignment> g_signals::parser::alignment_change{nullptr};
|
callback<const alignment> g_signals::parser::alignment_change{nullptr};
|
||||||
callback<attribute> g_signals::parser::attribute_set{nullptr};
|
callback<const attribute> g_signals::parser::attribute_set{nullptr};
|
||||||
callback<attribute> g_signals::parser::attribute_unset{nullptr};
|
callback<const attribute> g_signals::parser::attribute_unset{nullptr};
|
||||||
callback<attribute> g_signals::parser::attribute_toggle{nullptr};
|
callback<const mousebtn, string> g_signals::parser::action_block_open{nullptr};
|
||||||
callback<mousebtn, string> g_signals::parser::action_block_open{nullptr};
|
callback<const mousebtn> g_signals::parser::action_block_close{nullptr};
|
||||||
callback<mousebtn> g_signals::parser::action_block_close{nullptr};
|
callback<const gc, const uint32_t> g_signals::parser::color_change{nullptr};
|
||||||
callback<gc, color> g_signals::parser::color_change{nullptr};
|
callback<const int8_t> g_signals::parser::font_change{nullptr};
|
||||||
callback<int> g_signals::parser::font_change{nullptr};
|
callback<const int16_t> g_signals::parser::pixel_offset{nullptr};
|
||||||
callback<int> g_signals::parser::pixel_offset{nullptr};
|
callback<const uint16_t> g_signals::parser::ascii_text_write{nullptr};
|
||||||
callback<uint16_t> g_signals::parser::ascii_text_write{nullptr};
|
callback<const uint16_t> g_signals::parser::unicode_text_write{nullptr};
|
||||||
callback<uint16_t> g_signals::parser::unicode_text_write{nullptr};
|
callback<const char*, const size_t> g_signals::parser::string_write{nullptr};
|
||||||
callback<const char*, size_t> g_signals::parser::string_write{nullptr};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals used to communicate with the tray manager
|
* Signals used to communicate with the tray manager
|
||||||
|
|
|
@ -115,9 +115,9 @@ namespace drawtypes {
|
||||||
// avoid color bleed
|
// avoid color bleed
|
||||||
if (icon_empty && icon_indicator) {
|
if (icon_empty && icon_indicator) {
|
||||||
if (!icon_indicator->m_background.empty() && icon_empty->m_background.empty())
|
if (!icon_indicator->m_background.empty() && icon_empty->m_background.empty())
|
||||||
icon_empty->m_background = bar.background.source();
|
icon_empty->m_background = color_util::hex<uint16_t>(bar.background);
|
||||||
if (!icon_indicator->m_foreground.empty() && icon_empty->m_foreground.empty())
|
if (!icon_indicator->m_foreground.empty() && icon_empty->m_foreground.empty())
|
||||||
icon_empty->m_foreground = bar.foreground.source();
|
icon_empty->m_foreground = color_util::hex<uint16_t>(bar.foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
progressbar->set_empty(move(icon_empty));
|
progressbar->set_empty(move(icon_empty));
|
||||||
|
|
|
@ -41,7 +41,7 @@ color::operator XRenderColor() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
color::operator string() const {
|
color::operator string() const {
|
||||||
return color_util::hex(m_color);
|
return color_util::hex<uint8_t>(m_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
color::operator uint32_t() const {
|
color::operator uint32_t() const {
|
||||||
|
|
|
@ -37,21 +37,7 @@ font_manager::~font_manager() {
|
||||||
m_fonts.clear();
|
m_fonts.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void font_manager::set_preferred_font(int index) {
|
bool font_manager::load(string name, int8_t fontindex, int8_t offset_y) {
|
||||||
if (index <= 0) {
|
|
||||||
m_fontindex = -1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto&& font : m_fonts) {
|
|
||||||
if (font.first == index) {
|
|
||||||
m_fontindex = index;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool font_manager::load(string name, int fontindex, int offset_y) {
|
|
||||||
if (fontindex != -1 && m_fonts.find(fontindex) != m_fonts.end()) {
|
if (fontindex != -1 && m_fonts.find(fontindex) != m_fonts.end()) {
|
||||||
m_logger.warn("A font with index '%i' has already been loaded, skip...", fontindex);
|
m_logger.warn("A font with index '%i' has already been loaded, skip...", fontindex);
|
||||||
return false;
|
return false;
|
||||||
|
@ -92,6 +78,20 @@ bool font_manager::load(string name, int fontindex, int offset_y) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void font_manager::set_preferred_font(int8_t index) {
|
||||||
|
if (index <= 0) {
|
||||||
|
m_fontindex = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto&& font : m_fonts) {
|
||||||
|
if (font.first == index) {
|
||||||
|
m_fontindex = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
font_t& font_manager::match_char(uint16_t chr) {
|
font_t& font_manager::match_char(uint16_t chr) {
|
||||||
static font_t notfound;
|
static font_t notfound;
|
||||||
if (!m_fonts.empty()) {
|
if (!m_fonts.empty()) {
|
||||||
|
@ -108,7 +108,7 @@ font_t& font_manager::match_char(uint16_t chr) {
|
||||||
return notfound;
|
return notfound;
|
||||||
}
|
}
|
||||||
|
|
||||||
int font_manager::char_width(font_t& font, uint16_t chr) {
|
uint8_t font_manager::char_width(font_t& font, uint16_t chr) {
|
||||||
if (!font)
|
if (!font)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -142,6 +142,28 @@ XftColor font_manager::xftcolor() {
|
||||||
return m_xftcolor;
|
return m_xftcolor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XftDraw* font_manager::xftdraw() {
|
||||||
|
return m_xftdraw;
|
||||||
|
}
|
||||||
|
|
||||||
|
XftDraw* font_manager::create_xftdraw(xcb_pixmap_t pm, xcb_colormap_t cm) {
|
||||||
|
m_xftdraw = XftDrawCreate(xlib::get_display(), pm, xlib::get_visual(), cm);
|
||||||
|
return m_xftdraw;
|
||||||
|
}
|
||||||
|
|
||||||
|
void font_manager::destroy_xftdraw() {
|
||||||
|
XftDrawDestroy(m_xftdraw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void font_manager::allocate_color(uint32_t color, bool initial_alloc) {
|
||||||
|
XRenderColor x;
|
||||||
|
x.red = color_util::red_channel<uint16_t>(color);
|
||||||
|
x.green = color_util::green_channel<uint16_t>(color);
|
||||||
|
x.blue = color_util::blue_channel<uint16_t>(color);
|
||||||
|
x.alpha = color_util::alpha_channel<uint16_t>(color);
|
||||||
|
allocate_color(x, initial_alloc);
|
||||||
|
}
|
||||||
|
|
||||||
void font_manager::allocate_color(XRenderColor color, bool initial_alloc) {
|
void font_manager::allocate_color(XRenderColor color, bool initial_alloc) {
|
||||||
if (!initial_alloc)
|
if (!initial_alloc)
|
||||||
XftColorFree(m_display, m_visual, m_colormap, &m_xftcolor);
|
XftColorFree(m_display, m_visual, m_colormap, &m_xftcolor);
|
||||||
|
@ -150,7 +172,7 @@ void font_manager::allocate_color(XRenderColor color, bool initial_alloc) {
|
||||||
m_logger.err("Failed to allocate color");
|
m_logger.err("Failed to allocate color");
|
||||||
}
|
}
|
||||||
|
|
||||||
void font_manager::set_gcontext_font(gcontext& gc, xcb_font_t font) {
|
void font_manager::set_gcontext_font(xcb_gcontext_t gc, xcb_font_t font) {
|
||||||
const uint32_t values[1]{font};
|
const uint32_t values[1]{font};
|
||||||
m_connection.change_gc(gc, XCB_GC_FONT, values);
|
m_connection.change_gc(gc, XCB_GC_FONT, values);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue