mirror of
https://github.com/rad4day/Waybar.git
synced 2023-12-21 10:22:59 +01:00
foreign-toplevel-manager based taskbar module (#692)
Co-authored-by: Alex <alexisr245@gmail.com>
This commit is contained in:
parent
e96a0bf799
commit
adaf843048
@ -7,6 +7,9 @@
|
|||||||
#include "modules/sway/window.hpp"
|
#include "modules/sway/window.hpp"
|
||||||
#include "modules/sway/workspaces.hpp"
|
#include "modules/sway/workspaces.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_WLR
|
||||||
|
#include "modules/wlr/taskbar.hpp"
|
||||||
|
#endif
|
||||||
#if defined(__linux__) && !defined(NO_FILESYSTEM)
|
#if defined(__linux__) && !defined(NO_FILESYSTEM)
|
||||||
#include "modules/battery.hpp"
|
#include "modules/battery.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
160
include/modules/wlr/taskbar.hpp
Normal file
160
include/modules/wlr/taskbar.hpp
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AModule.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "client.hpp"
|
||||||
|
#include "util/json.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <gdk/gdk.h>
|
||||||
|
|
||||||
|
#include <glibmm/refptr.h>
|
||||||
|
|
||||||
|
#include <gtkmm/box.h>
|
||||||
|
#include <gtkmm/button.h>
|
||||||
|
#include <gtkmm/image.h>
|
||||||
|
#include <gtkmm/label.h>
|
||||||
|
#include <gtkmm/icontheme.h>
|
||||||
|
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace waybar::modules::wlr {
|
||||||
|
|
||||||
|
class Taskbar;
|
||||||
|
|
||||||
|
class Task
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Task(const waybar::Bar&, const Json::Value&, Taskbar*,
|
||||||
|
struct zwlr_foreign_toplevel_handle_v1 *, struct wl_seat*);
|
||||||
|
~Task();
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum State {
|
||||||
|
MAXIMIZED = (1 << 0),
|
||||||
|
MINIMIZED = (1 << 1),
|
||||||
|
ACTIVE = (1 << 2),
|
||||||
|
FULLSCREEN = (1 << 3),
|
||||||
|
INVALID = (1 << 4)
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
static uint32_t global_id;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const waybar::Bar &bar_;
|
||||||
|
const Json::Value &config_;
|
||||||
|
Taskbar *tbar_;
|
||||||
|
struct zwlr_foreign_toplevel_handle_v1 *handle_;
|
||||||
|
struct wl_seat *seat_;
|
||||||
|
|
||||||
|
uint32_t id_;
|
||||||
|
|
||||||
|
Gtk::Button button_;
|
||||||
|
Gtk::Box content_;
|
||||||
|
Gtk::Image icon_;
|
||||||
|
Gtk::Label text_before_;
|
||||||
|
Gtk::Label text_after_;
|
||||||
|
bool button_visible_;
|
||||||
|
|
||||||
|
bool with_icon_;
|
||||||
|
std::string format_before_;
|
||||||
|
std::string format_after_;
|
||||||
|
|
||||||
|
std::string format_tooltip_;
|
||||||
|
|
||||||
|
std::string title_;
|
||||||
|
std::string app_id_;
|
||||||
|
uint32_t state_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string repr() const;
|
||||||
|
std::string state_string(bool = false) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/* Getter functions */
|
||||||
|
uint32_t id() const { return id_; }
|
||||||
|
std::string title() const { return title_; }
|
||||||
|
std::string app_id() const { return app_id_; }
|
||||||
|
uint32_t state() const { return state_; }
|
||||||
|
bool maximized() const { return state_ & MAXIMIZED; }
|
||||||
|
bool minimized() const { return state_ & MINIMIZED; }
|
||||||
|
bool active() const { return state_ & ACTIVE; }
|
||||||
|
bool fullscreen() const { return state_ & FULLSCREEN; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
/* Callbacks for the wlr protocol */
|
||||||
|
void handle_title(const char *);
|
||||||
|
void handle_app_id(const char *);
|
||||||
|
void handle_output_enter(struct wl_output *);
|
||||||
|
void handle_output_leave(struct wl_output *);
|
||||||
|
void handle_state(struct wl_array *);
|
||||||
|
void handle_done();
|
||||||
|
void handle_closed();
|
||||||
|
|
||||||
|
/* Callbacks for Gtk events */
|
||||||
|
bool handle_clicked(GdkEventButton *);
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool operator==(const Task&) const;
|
||||||
|
bool operator!=(const Task&) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void update();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/* Interaction with the tasks */
|
||||||
|
void maximize(bool);
|
||||||
|
void minimize(bool);
|
||||||
|
void activate();
|
||||||
|
void fullscreen(bool);
|
||||||
|
void close();
|
||||||
|
};
|
||||||
|
|
||||||
|
using TaskPtr = std::unique_ptr<Task>;
|
||||||
|
|
||||||
|
|
||||||
|
class Taskbar : public waybar::AModule
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Taskbar(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||||
|
~Taskbar();
|
||||||
|
void update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const waybar::Bar &bar_;
|
||||||
|
Gtk::Box box_;
|
||||||
|
std::vector<TaskPtr> tasks_;
|
||||||
|
|
||||||
|
Glib::RefPtr<Gtk::IconTheme> icon_theme_;
|
||||||
|
|
||||||
|
struct zwlr_foreign_toplevel_manager_v1 *manager_;
|
||||||
|
struct wl_seat *seat_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/* Callbacks for global registration */
|
||||||
|
void register_manager(struct wl_registry*, uint32_t name, uint32_t version);
|
||||||
|
void register_seat(struct wl_registry*, uint32_t name, uint32_t version);
|
||||||
|
|
||||||
|
/* Callbacks for the wlr protocol */
|
||||||
|
void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1 *);
|
||||||
|
void handle_finished();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void add_button(Gtk::Button &);
|
||||||
|
void move_button(Gtk::Button &, int);
|
||||||
|
void remove_button(Gtk::Button &);
|
||||||
|
void remove_task(uint32_t);
|
||||||
|
|
||||||
|
bool show_output(struct wl_output *) const;
|
||||||
|
bool all_outputs() const;
|
||||||
|
|
||||||
|
Glib::RefPtr<Gtk::IconTheme> icon_theme() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace waybar::modules::wlr */
|
104
man/waybar-wlr-taskbar.5.scd
Normal file
104
man/waybar-wlr-taskbar.5.scd
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
waybar-wlr-taskbar(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
wlroots - Taskbar module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *taskbar* module displays the currently open applications. This module requires
|
||||||
|
a compositor that implements the foreign-toplevel-manager interface.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
Addressed by *wlr/taskbar*
|
||||||
|
|
||||||
|
*all-outputs*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to false applications on the waybar's current output will be shown. Otherwise all applications are shown.
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {icon} ++
|
||||||
|
The format, how information should be displayed.
|
||||||
|
|
||||||
|
*icon-theme*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The name of the icon-theme that should be used. If omitted, the system default will be used.
|
||||||
|
|
||||||
|
*icon-size*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
default: 16 ++
|
||||||
|
The size of the icon.
|
||||||
|
|
||||||
|
*tooltip*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: true ++
|
||||||
|
If set to false no tooltip will be shown.
|
||||||
|
|
||||||
|
*tooltip-format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {title} ++
|
||||||
|
The format, how information in the tooltip should be displayed.
|
||||||
|
|
||||||
|
*active-first*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder.
|
||||||
|
|
||||||
|
*on-click*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The action which should be triggered when clicking on the application button with the left mouse button.
|
||||||
|
|
||||||
|
*on-click-middle*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The action which should be triggered when clicking on the application button with the middle mouse button.
|
||||||
|
|
||||||
|
*on-click-right*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The action which should be triggered when clicking on the application button with the right mouse button.
|
||||||
|
|
||||||
|
*on-update*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when the module is updated.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{icon}*: The icon of the application.
|
||||||
|
|
||||||
|
*{title}*: The title of the application.
|
||||||
|
|
||||||
|
*{app_id}*: The app_id (== application name) of the application.
|
||||||
|
|
||||||
|
*{state}*: The state (minimized, maximized, active, fullscreen) of the application.
|
||||||
|
|
||||||
|
*{short_state}*: The state (minimize == m, maximized == M, active == A, fullscreen == F) represented as one character of the application.
|
||||||
|
|
||||||
|
# CLICK ACTIONS
|
||||||
|
|
||||||
|
*activate*: Bring the application into foreground.
|
||||||
|
*minimize*: Minimize the application.
|
||||||
|
*maximize*: Maximize the application.
|
||||||
|
*fullscreen*: Set the application to fullscreen.
|
||||||
|
*close*: Close the application.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"wlr/taskbar": {
|
||||||
|
"format": "{icon}",
|
||||||
|
"tooltip-format": "{title}",
|
||||||
|
"on-click": "activate",
|
||||||
|
"on-middle-click": "close"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Style
|
||||||
|
|
||||||
|
- *#taskbar*
|
||||||
|
- *#taskbar button*
|
||||||
|
- *#taskbar button.maximized*
|
||||||
|
- *#taskbar button.minimized*
|
||||||
|
- *#taskbar button.active*
|
||||||
|
- *#taskbar button.fullscreen*
|
@ -196,5 +196,6 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
|
|||||||
- *waybar-sway-mode(5)*
|
- *waybar-sway-mode(5)*
|
||||||
- *waybar-sway-window(5)*
|
- *waybar-sway-window(5)*
|
||||||
- *waybar-sway-workspaces(5)*
|
- *waybar-sway-workspaces(5)*
|
||||||
|
- *waybar-wlr-taskbar(5)*
|
||||||
- *waybar-temperature(5)*
|
- *waybar-temperature(5)*
|
||||||
- *waybar-tray(5)*
|
- *waybar-tray(5)*
|
||||||
|
@ -165,6 +165,11 @@ if true # find_program('sway', required : false).found()
|
|||||||
]
|
]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if true
|
||||||
|
add_project_arguments('-DHAVE_WLR', language: 'cpp')
|
||||||
|
src_files += 'src/modules/wlr/taskbar.cpp'
|
||||||
|
endif
|
||||||
|
|
||||||
if libnl.found() and libnlgen.found()
|
if libnl.found() and libnlgen.found()
|
||||||
add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
|
add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
|
||||||
src_files += 'src/modules/network.cpp'
|
src_files += 'src/modules/network.cpp'
|
||||||
@ -260,6 +265,7 @@ if scdoc.found()
|
|||||||
'waybar-temperature.5.scd',
|
'waybar-temperature.5.scd',
|
||||||
'waybar-tray.5.scd',
|
'waybar-tray.5.scd',
|
||||||
'waybar-states.5.scd',
|
'waybar-states.5.scd',
|
||||||
|
'waybar-wlr-taskbar.5.scd',
|
||||||
'waybar-bluetooth.5.scd',
|
'waybar-bluetooth.5.scd',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ client_protocols = [
|
|||||||
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
|
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
|
||||||
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
|
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
|
||||||
['wlr-layer-shell-unstable-v1.xml'],
|
['wlr-layer-shell-unstable-v1.xml'],
|
||||||
|
['wlr-foreign-toplevel-management-unstable-v1.xml'],
|
||||||
]
|
]
|
||||||
|
|
||||||
client_protos_src = []
|
client_protos_src = []
|
||||||
|
259
protocol/wlr-foreign-toplevel-management-unstable-v1.xml
Normal file
259
protocol/wlr-foreign-toplevel-management-unstable-v1.xml
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="wlr_foreign_toplevel_management_unstable_v1">
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2018 Ilia Bozhinov
|
||||||
|
|
||||||
|
Permission to use, copy, modify, distribute, and sell this
|
||||||
|
software and its documentation for any purpose is hereby granted
|
||||||
|
without fee, provided that the above copyright notice appear in
|
||||||
|
all copies and that both that copyright notice and this permission
|
||||||
|
notice appear in supporting documentation, and that the name of
|
||||||
|
the copyright holders not be used in advertising or publicity
|
||||||
|
pertaining to distribution of the software without specific,
|
||||||
|
written prior permission. The copyright holders make no
|
||||||
|
representations about the suitability of this software for any
|
||||||
|
purpose. It is provided "as is" without express or implied
|
||||||
|
warranty.
|
||||||
|
|
||||||
|
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||||
|
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||||
|
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||||
|
THIS SOFTWARE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<interface name="zwlr_foreign_toplevel_manager_v1" version="2">
|
||||||
|
<description summary="list and control opened apps">
|
||||||
|
The purpose of this protocol is to enable the creation of taskbars
|
||||||
|
and docks by providing them with a list of opened applications and
|
||||||
|
letting them request certain actions on them, like maximizing, etc.
|
||||||
|
|
||||||
|
After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
|
||||||
|
toplevel window will be sent via the toplevel event
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="toplevel">
|
||||||
|
<description summary="a toplevel has been created">
|
||||||
|
This event is emitted whenever a new toplevel window is created. It
|
||||||
|
is emitted for all toplevels, regardless of the app that has created
|
||||||
|
them.
|
||||||
|
|
||||||
|
All initial details of the toplevel(title, app_id, states, etc.) will
|
||||||
|
be sent immediately after this event via the corresponding events in
|
||||||
|
zwlr_foreign_toplevel_handle_v1.
|
||||||
|
</description>
|
||||||
|
<arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="stop">
|
||||||
|
<description summary="stop sending events">
|
||||||
|
Indicates the client no longer wishes to receive events for new toplevels.
|
||||||
|
However the compositor may emit further toplevel_created events, until
|
||||||
|
the finished event is emitted.
|
||||||
|
|
||||||
|
The client must not send any more requests after this one.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="finished">
|
||||||
|
<description summary="the compositor has finished with the toplevel manager">
|
||||||
|
This event indicates that the compositor is done sending events to the
|
||||||
|
zwlr_foreign_toplevel_manager_v1. The server will destroy the object
|
||||||
|
immediately after sending this request, so it will become invalid and
|
||||||
|
the client should free any resources associated with it.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwlr_foreign_toplevel_handle_v1" version="2">
|
||||||
|
<description summary="an opened toplevel">
|
||||||
|
A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
|
||||||
|
window. Each app may have multiple opened toplevels.
|
||||||
|
|
||||||
|
Each toplevel has a list of outputs it is visible on, conveyed to the
|
||||||
|
client with the output_enter and output_leave events.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="title">
|
||||||
|
<description summary="title change">
|
||||||
|
This event is emitted whenever the title of the toplevel changes.
|
||||||
|
</description>
|
||||||
|
<arg name="title" type="string"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="app_id">
|
||||||
|
<description summary="app-id change">
|
||||||
|
This event is emitted whenever the app-id of the toplevel changes.
|
||||||
|
</description>
|
||||||
|
<arg name="app_id" type="string"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="output_enter">
|
||||||
|
<description summary="toplevel entered an output">
|
||||||
|
This event is emitted whenever the toplevel becomes visible on
|
||||||
|
the given output. A toplevel may be visible on multiple outputs.
|
||||||
|
</description>
|
||||||
|
<arg name="output" type="object" interface="wl_output"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="output_leave">
|
||||||
|
<description summary="toplevel left an output">
|
||||||
|
This event is emitted whenever the toplevel stops being visible on
|
||||||
|
the given output. It is guaranteed that an entered-output event
|
||||||
|
with the same output has been emitted before this event.
|
||||||
|
</description>
|
||||||
|
<arg name="output" type="object" interface="wl_output"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="set_maximized">
|
||||||
|
<description summary="requests that the toplevel be maximized">
|
||||||
|
Requests that the toplevel be maximized. If the maximized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="unset_maximized">
|
||||||
|
<description summary="requests that the toplevel be unmaximized">
|
||||||
|
Requests that the toplevel be unmaximized. If the maximized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_minimized">
|
||||||
|
<description summary="requests that the toplevel be minimized">
|
||||||
|
Requests that the toplevel be minimized. If the minimized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="unset_minimized">
|
||||||
|
<description summary="requests that the toplevel be unminimized">
|
||||||
|
Requests that the toplevel be unminimized. If the minimized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="activate">
|
||||||
|
<description summary="activate the toplevel">
|
||||||
|
Request that this toplevel be activated on the given seat.
|
||||||
|
There is no guarantee the toplevel will be actually activated.
|
||||||
|
</description>
|
||||||
|
<arg name="seat" type="object" interface="wl_seat"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="state">
|
||||||
|
<description summary="types of states on the toplevel">
|
||||||
|
The different states that a toplevel can have. These have the same meaning
|
||||||
|
as the states with the same names defined in xdg-toplevel
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<entry name="maximized" value="0" summary="the toplevel is maximized"/>
|
||||||
|
<entry name="minimized" value="1" summary="the toplevel is minimized"/>
|
||||||
|
<entry name="activated" value="2" summary="the toplevel is active"/>
|
||||||
|
<entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="state">
|
||||||
|
<description summary="the toplevel state changed">
|
||||||
|
This event is emitted immediately after the zlw_foreign_toplevel_handle_v1
|
||||||
|
is created and each time the toplevel state changes, either because of a
|
||||||
|
compositor action or because of a request in this protocol.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<arg name="state" type="array"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="done">
|
||||||
|
<description summary="all information about the toplevel has been sent">
|
||||||
|
This event is sent after all changes in the toplevel state have been
|
||||||
|
sent.
|
||||||
|
|
||||||
|
This allows changes to the zwlr_foreign_toplevel_handle_v1 properties
|
||||||
|
to be seen as atomic, even if they happen via multiple events.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="close">
|
||||||
|
<description summary="request that the toplevel be closed">
|
||||||
|
Send a request to the toplevel to close itself. The compositor would
|
||||||
|
typically use a shell-specific method to carry out this request, for
|
||||||
|
example by sending the xdg_toplevel.close event. However, this gives
|
||||||
|
no guarantees the toplevel will actually be destroyed. If and when
|
||||||
|
this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
|
||||||
|
be emitted.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_rectangle">
|
||||||
|
<description summary="the rectangle which represents the toplevel">
|
||||||
|
The rectangle of the surface specified in this request corresponds to
|
||||||
|
the place where the app using this protocol represents the given toplevel.
|
||||||
|
It can be used by the compositor as a hint for some operations, e.g
|
||||||
|
minimizing. The client is however not required to set this, in which
|
||||||
|
case the compositor is free to decide some default value.
|
||||||
|
|
||||||
|
If the client specifies more than one rectangle, only the last one is
|
||||||
|
considered.
|
||||||
|
|
||||||
|
The dimensions are given in surface-local coordinates.
|
||||||
|
Setting width=height=0 removes the already-set rectangle.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<arg name="surface" type="object" interface="wl_surface"/>
|
||||||
|
<arg name="x" type="int"/>
|
||||||
|
<arg name="y" type="int"/>
|
||||||
|
<arg name="width" type="int"/>
|
||||||
|
<arg name="height" type="int"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="invalid_rectangle" value="0"
|
||||||
|
summary="the provided rectangle is invalid"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="closed">
|
||||||
|
<description summary="this toplevel has been destroyed">
|
||||||
|
This event means the toplevel has been destroyed. It is guaranteed there
|
||||||
|
won't be any more events for this zwlr_foreign_toplevel_handle_v1. The
|
||||||
|
toplevel itself becomes inert so any requests will be ignored except the
|
||||||
|
destroy request.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
|
||||||
|
Destroys the zwlr_foreign_toplevel_handle_v1 object.
|
||||||
|
|
||||||
|
This request should be called either when the client does not want to
|
||||||
|
use the toplevel anymore or after the closed event to finalize the
|
||||||
|
destruction of the object.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<!-- Version 2 additions -->
|
||||||
|
|
||||||
|
<request name="set_fullscreen" since="2">
|
||||||
|
<description summary="request that the toplevel be fullscreened">
|
||||||
|
Requests that the toplevel be fullscreened on the given output. If the
|
||||||
|
fullscreen state and/or the outputs the toplevel is visible on actually
|
||||||
|
change, this will be indicated by the state and output_enter/leave
|
||||||
|
events.
|
||||||
|
|
||||||
|
The output parameter is only a hint to the compositor. Also, if output
|
||||||
|
is NULL, the compositor should decide which output the toplevel will be
|
||||||
|
fullscreened on, if at all.
|
||||||
|
</description>
|
||||||
|
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="unset_fullscreen" since="2">
|
||||||
|
<description summary="request that the toplevel be unfullscreened">
|
||||||
|
Requests that the toplevel be unfullscreened. If the fullscreen state
|
||||||
|
actually changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
@ -22,6 +22,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
|||||||
if (ref == "sway/window") {
|
if (ref == "sway/window") {
|
||||||
return new waybar::modules::sway::Window(id, bar_, config_[name]);
|
return new waybar::modules::sway::Window(id, bar_, config_[name]);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_WLR
|
||||||
|
if (ref == "wlr/taskbar") {
|
||||||
|
return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (ref == "idle_inhibitor") {
|
if (ref == "idle_inhibitor") {
|
||||||
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
|
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
|
||||||
|
658
src/modules/wlr/taskbar.cpp
Normal file
658
src/modules/wlr/taskbar.cpp
Normal file
@ -0,0 +1,658 @@
|
|||||||
|
#include "modules/wlr/taskbar.hpp"
|
||||||
|
|
||||||
|
#include "glibmm/refptr.h"
|
||||||
|
#include "util/format.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <gdkmm/monitor.h>
|
||||||
|
|
||||||
|
#include <gtkmm/icontheme.h>
|
||||||
|
|
||||||
|
#include <giomm/desktopappinfo.h>
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace waybar::modules::wlr {
|
||||||
|
|
||||||
|
/* Icon loading functions */
|
||||||
|
|
||||||
|
/* Method 1 - get the correct icon name from the desktop file */
|
||||||
|
static std::string get_from_desktop_app_info(const std::string &app_id)
|
||||||
|
{
|
||||||
|
Glib::RefPtr<Gio::DesktopAppInfo> app_info;
|
||||||
|
|
||||||
|
std::vector<std::string> prefixes = {
|
||||||
|
"",
|
||||||
|
"/usr/share/applications/",
|
||||||
|
"/usr/share/applications/kde/",
|
||||||
|
"/usr/share/applications/org.kde.",
|
||||||
|
"/usr/local/share/applications/",
|
||||||
|
"/usr/local/share/applications/org.kde.",
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string lower_app_id = app_id;
|
||||||
|
std::transform(std::begin(lower_app_id), std::end(lower_app_id), std::begin(lower_app_id),
|
||||||
|
[](unsigned char c) { return std::tolower(c); });
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::string> app_id_variations = {
|
||||||
|
app_id,
|
||||||
|
lower_app_id
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> suffixes = {
|
||||||
|
"",
|
||||||
|
".desktop"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& prefix : prefixes)
|
||||||
|
for (auto& id : app_id_variations)
|
||||||
|
for (auto& suffix : suffixes)
|
||||||
|
if (!app_info)
|
||||||
|
app_info = Gio::DesktopAppInfo::create_from_filename(prefix + id + suffix);
|
||||||
|
|
||||||
|
if (app_info)
|
||||||
|
return app_info->get_icon()->to_string();
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */
|
||||||
|
static std::string get_from_icon_theme(Glib::RefPtr<Gtk::IconTheme> icon_theme,
|
||||||
|
const std::string &app_id) {
|
||||||
|
|
||||||
|
if (icon_theme->lookup_icon(app_id, 24))
|
||||||
|
return app_id;
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool image_load_icon(Gtk::Image& image, Glib::RefPtr<Gtk::IconTheme> icon_theme,
|
||||||
|
const std::string &app_id_list, int size)
|
||||||
|
{
|
||||||
|
std::string app_id;
|
||||||
|
std::istringstream stream(app_id_list);
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
|
||||||
|
/* Wayfire sends a list of app-id's in space separated format, other compositors
|
||||||
|
* send a single app-id, but in any case this works fine */
|
||||||
|
while (stream >> app_id)
|
||||||
|
{
|
||||||
|
std::string icon_name = get_from_desktop_app_info(app_id);
|
||||||
|
if (icon_name.empty())
|
||||||
|
icon_name = get_from_icon_theme(icon_theme, app_id);
|
||||||
|
|
||||||
|
if (icon_name.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
|
||||||
|
if (pixbuf) {
|
||||||
|
image.set(pixbuf);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Task class implementation */
|
||||||
|
uint32_t Task::global_id = 0;
|
||||||
|
|
||||||
|
static void tl_handle_title(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
|
||||||
|
const char *title)
|
||||||
|
{
|
||||||
|
return static_cast<Task*>(data)->handle_title(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tl_handle_app_id(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
|
||||||
|
const char *app_id)
|
||||||
|
{
|
||||||
|
return static_cast<Task*>(data)->handle_app_id(app_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tl_handle_output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
|
||||||
|
struct wl_output *output)
|
||||||
|
{
|
||||||
|
return static_cast<Task*>(data)->handle_output_enter(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tl_handle_output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
|
||||||
|
struct wl_output *output)
|
||||||
|
{
|
||||||
|
return static_cast<Task*>(data)->handle_output_leave(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tl_handle_state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
|
||||||
|
struct wl_array *state)
|
||||||
|
{
|
||||||
|
return static_cast<Task*>(data)->handle_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tl_handle_done(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
|
||||||
|
{
|
||||||
|
return static_cast<Task*>(data)->handle_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tl_handle_closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
|
||||||
|
{
|
||||||
|
return static_cast<Task*>(data)->handle_closed();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_impl = {
|
||||||
|
.title = tl_handle_title,
|
||||||
|
.app_id = tl_handle_app_id,
|
||||||
|
.output_enter = tl_handle_output_enter,
|
||||||
|
.output_leave = tl_handle_output_leave,
|
||||||
|
.state = tl_handle_state,
|
||||||
|
.done = tl_handle_done,
|
||||||
|
.closed = tl_handle_closed,
|
||||||
|
};
|
||||||
|
|
||||||
|
Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
|
||||||
|
struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) :
|
||||||
|
bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat},
|
||||||
|
id_{global_id++},
|
||||||
|
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
|
||||||
|
button_visible_{false}
|
||||||
|
{
|
||||||
|
zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
|
||||||
|
|
||||||
|
button_.set_relief(Gtk::RELIEF_NONE);
|
||||||
|
|
||||||
|
content_.add(text_before_);
|
||||||
|
content_.add(icon_);
|
||||||
|
content_.add(text_after_);
|
||||||
|
|
||||||
|
content_.show();
|
||||||
|
button_.add(content_);
|
||||||
|
|
||||||
|
with_icon_ = false;
|
||||||
|
format_before_.clear();
|
||||||
|
format_after_.clear();
|
||||||
|
|
||||||
|
if (config_["format"].isString()) {
|
||||||
|
/* The user defined a format string, use it */
|
||||||
|
auto format = config_["format"].asString();
|
||||||
|
|
||||||
|
auto icon_pos = format.find("{icon}");
|
||||||
|
if (icon_pos == 0) {
|
||||||
|
with_icon_ = true;
|
||||||
|
format_after_ = format.substr(6);
|
||||||
|
} else if (icon_pos == std::string::npos) {
|
||||||
|
format_after_ = format;
|
||||||
|
} else {
|
||||||
|
with_icon_ = true;
|
||||||
|
format_before_ = format.substr(0, icon_pos);
|
||||||
|
format_after_ = format.substr(icon_pos + 6);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* The default is to only show the icon */
|
||||||
|
with_icon_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Strip spaces at the beginning and end of the format strings */
|
||||||
|
if (!format_before_.empty() && format_before_.back() == ' ')
|
||||||
|
format_before_.pop_back();
|
||||||
|
if (!format_after_.empty() && format_after_.front() == ' ')
|
||||||
|
format_after_.erase(std::cbegin(format_after_));
|
||||||
|
|
||||||
|
format_tooltip_.clear();
|
||||||
|
if (!config_["tooltip"].isBool() || config_["tooltip"].asBool()) {
|
||||||
|
if (config_["tooltip-format"].isString())
|
||||||
|
format_tooltip_ = config_["tooltip-format"].asString();
|
||||||
|
else
|
||||||
|
format_tooltip_ = "{title}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle click events if configured */
|
||||||
|
if (config_["on-click"].isString() || config_["on-click-middle"].isString()
|
||||||
|
|| config_["on-click-left"].isString()) {
|
||||||
|
button_.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||||
|
button_.signal_button_press_event().connect(
|
||||||
|
sigc::mem_fun(*this, &Task::handle_clicked), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Task::~Task()
|
||||||
|
{
|
||||||
|
if (handle_) {
|
||||||
|
zwlr_foreign_toplevel_handle_v1_destroy(handle_);
|
||||||
|
handle_ = nullptr;
|
||||||
|
}
|
||||||
|
if (button_visible_) {
|
||||||
|
tbar_->remove_button(button_);
|
||||||
|
button_visible_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Task::repr() const
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Task (" << id_ << ") " << title_ << " [" << app_id_ << "] <"
|
||||||
|
<< (active() ? "A" : "a")
|
||||||
|
<< (maximized() ? "M" : "m")
|
||||||
|
<< (minimized() ? "I" : "i")
|
||||||
|
<< (fullscreen() ? "F" : "f")
|
||||||
|
<< ">";
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Task::state_string(bool shortened) const
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
if (shortened)
|
||||||
|
ss << (minimized() ? "m" : "") << (maximized() ? "M" : "")
|
||||||
|
<< (active() ? "A" : "") << (fullscreen() ? "F" : "");
|
||||||
|
else
|
||||||
|
ss << (minimized() ? "minimized " : "") << (maximized() ? "maximized " : "")
|
||||||
|
<< (active() ? "active " : "") << (fullscreen() ? "fullscreen " : "");
|
||||||
|
|
||||||
|
std::string res = ss.str();
|
||||||
|
if (shortened || res.empty())
|
||||||
|
return res;
|
||||||
|
else
|
||||||
|
return res.substr(0, res.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::handle_title(const char *title)
|
||||||
|
{
|
||||||
|
title_ = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::handle_app_id(const char *app_id)
|
||||||
|
{
|
||||||
|
app_id_ = app_id;
|
||||||
|
if (!image_load_icon(icon_, tbar_->icon_theme(), app_id_,
|
||||||
|
config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16))
|
||||||
|
spdlog::warn("Failed to load icon for {}", app_id);
|
||||||
|
|
||||||
|
if (with_icon_)
|
||||||
|
icon_.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::handle_output_enter(struct wl_output *output)
|
||||||
|
{
|
||||||
|
spdlog::debug("{} entered output {}", repr(), (void*)output);
|
||||||
|
|
||||||
|
if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {
|
||||||
|
/* The task entered the output of the current bar make the button visible */
|
||||||
|
tbar_->add_button(button_);
|
||||||
|
button_.show();
|
||||||
|
button_visible_ = true;
|
||||||
|
spdlog::debug("{} now visible on {}", repr(), bar_.output->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::handle_output_leave(struct wl_output *output)
|
||||||
|
{
|
||||||
|
spdlog::debug("{} left output {}", repr(), (void*)output);
|
||||||
|
|
||||||
|
if (button_visible_ && !tbar_->all_outputs() && tbar_->show_output(output)) {
|
||||||
|
/* The task left the output of the current bar, make the button invisible */
|
||||||
|
tbar_->remove_button(button_);
|
||||||
|
button_.hide();
|
||||||
|
button_visible_ = false;
|
||||||
|
spdlog::debug("{} now invisible on {}", repr(), bar_.output->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::handle_state(struct wl_array *state)
|
||||||
|
{
|
||||||
|
state_ = 0;
|
||||||
|
for (uint32_t* entry = static_cast<uint32_t*>(state->data);
|
||||||
|
entry < static_cast<uint32_t*>(state->data) + state->size;
|
||||||
|
entry++) {
|
||||||
|
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED)
|
||||||
|
state_ |= MAXIMIZED;
|
||||||
|
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED)
|
||||||
|
state_ |= MINIMIZED;
|
||||||
|
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)
|
||||||
|
state_ |= ACTIVE;
|
||||||
|
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)
|
||||||
|
state_ |= FULLSCREEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::handle_done()
|
||||||
|
{
|
||||||
|
spdlog::debug("{} changed", repr());
|
||||||
|
|
||||||
|
if (state_ & MAXIMIZED) {
|
||||||
|
button_.get_style_context()->add_class("maximized");
|
||||||
|
} else if (!(state_ & MAXIMIZED)) {
|
||||||
|
button_.get_style_context()->remove_class("maximized");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ & MINIMIZED) {
|
||||||
|
button_.get_style_context()->add_class("minimized");
|
||||||
|
} else if (!(state_ & MINIMIZED)) {
|
||||||
|
button_.get_style_context()->remove_class("minimized");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ & ACTIVE) {
|
||||||
|
button_.get_style_context()->add_class("active");
|
||||||
|
} else if (!(state_ & ACTIVE)) {
|
||||||
|
button_.get_style_context()->remove_class("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ & FULLSCREEN) {
|
||||||
|
button_.get_style_context()->add_class("fullscreen");
|
||||||
|
} else if (!(state_ & FULLSCREEN)) {
|
||||||
|
button_.get_style_context()->remove_class("fullscreen");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_["active-first"].isBool() && config_["active-first"].asBool() && active())
|
||||||
|
tbar_->move_button(button_, 0);
|
||||||
|
|
||||||
|
tbar_->dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::handle_closed()
|
||||||
|
{
|
||||||
|
spdlog::debug("{} closed", repr());
|
||||||
|
zwlr_foreign_toplevel_handle_v1_destroy(handle_);
|
||||||
|
handle_ = nullptr;
|
||||||
|
if (button_visible_) {
|
||||||
|
tbar_->remove_button(button_);
|
||||||
|
button_visible_ = false;
|
||||||
|
}
|
||||||
|
tbar_->remove_task(id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Task::handle_clicked(GdkEventButton *bt)
|
||||||
|
{
|
||||||
|
std::string action;
|
||||||
|
if (config_["on-click"].isString() && bt->button == 1)
|
||||||
|
action = config_["on-click"].asString();
|
||||||
|
else if (config_["on-click-middle"].isString() && bt->button == 2)
|
||||||
|
action = config_["on-click-middle"].asString();
|
||||||
|
else if (config_["on-click-right"].isString() && bt->button == 3)
|
||||||
|
action = config_["on-click-right"].asString();
|
||||||
|
|
||||||
|
if (action.empty())
|
||||||
|
return true;
|
||||||
|
else if (action == "activate")
|
||||||
|
activate();
|
||||||
|
else if (action == "minimize")
|
||||||
|
minimize(!minimized());
|
||||||
|
else if (action == "maximize")
|
||||||
|
maximize(!maximized());
|
||||||
|
else if (action == "fullscreen")
|
||||||
|
fullscreen(!fullscreen());
|
||||||
|
else if (action == "close")
|
||||||
|
close();
|
||||||
|
else
|
||||||
|
spdlog::warn("Unknown action {}", action);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Task::operator==(const Task &o) const
|
||||||
|
{
|
||||||
|
return o.id_ == id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Task::operator!=(const Task &o) const
|
||||||
|
{
|
||||||
|
return o.id_ != id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::update()
|
||||||
|
{
|
||||||
|
if (!format_before_.empty()) {
|
||||||
|
text_before_.set_label(
|
||||||
|
fmt::format(format_before_,
|
||||||
|
fmt::arg("title", title_),
|
||||||
|
fmt::arg("app_id", app_id_),
|
||||||
|
fmt::arg("state", state_string()),
|
||||||
|
fmt::arg("short_state", state_string(true))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
text_before_.show();
|
||||||
|
}
|
||||||
|
if (!format_after_.empty()) {
|
||||||
|
text_after_.set_label(
|
||||||
|
fmt::format(format_before_,
|
||||||
|
fmt::arg("title", title_),
|
||||||
|
fmt::arg("app_id", app_id_),
|
||||||
|
fmt::arg("state", state_string()),
|
||||||
|
fmt::arg("short_state", state_string(true))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
text_after_.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!format_tooltip_.empty()) {
|
||||||
|
button_.set_tooltip_markup(
|
||||||
|
fmt::format(format_tooltip_,
|
||||||
|
fmt::arg("title", title_),
|
||||||
|
fmt::arg("app_id", app_id_),
|
||||||
|
fmt::arg("state", state_string()),
|
||||||
|
fmt::arg("short_state", state_string(true))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::maximize(bool set)
|
||||||
|
{
|
||||||
|
if (set)
|
||||||
|
zwlr_foreign_toplevel_handle_v1_set_maximized(handle_);
|
||||||
|
else
|
||||||
|
zwlr_foreign_toplevel_handle_v1_unset_maximized(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::minimize(bool set)
|
||||||
|
{
|
||||||
|
if (set)
|
||||||
|
zwlr_foreign_toplevel_handle_v1_set_minimized(handle_);
|
||||||
|
else
|
||||||
|
zwlr_foreign_toplevel_handle_v1_unset_minimized(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::activate()
|
||||||
|
{
|
||||||
|
zwlr_foreign_toplevel_handle_v1_activate(handle_, seat_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::fullscreen(bool set)
|
||||||
|
{
|
||||||
|
if (set)
|
||||||
|
zwlr_foreign_toplevel_handle_v1_set_fullscreen(handle_, nullptr);
|
||||||
|
else
|
||||||
|
zwlr_foreign_toplevel_handle_v1_unset_fullscreen(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Task::close()
|
||||||
|
{
|
||||||
|
zwlr_foreign_toplevel_handle_v1_close(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Taskbar class implementation */
|
||||||
|
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
|
||||||
|
const char *interface, uint32_t version)
|
||||||
|
{
|
||||||
|
if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {
|
||||||
|
static_cast<Taskbar*>(data)->register_manager(registry, name, version);
|
||||||
|
} else if (std::strcmp(interface, wl_seat_interface.name) == 0) {
|
||||||
|
static_cast<Taskbar*>(data)->register_seat(registry, name, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
||||||
|
{
|
||||||
|
/* Nothing to do here */
|
||||||
|
}
|
||||||
|
|
||||||
|
static const wl_registry_listener registry_listener_impl = {
|
||||||
|
.global = handle_global,
|
||||||
|
.global_remove = handle_global_remove
|
||||||
|
};
|
||||||
|
|
||||||
|
Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
|
||||||
|
: waybar::AModule(config, "taskbar", id, false, false),
|
||||||
|
bar_(bar),
|
||||||
|
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
|
||||||
|
manager_{nullptr}, seat_{nullptr}
|
||||||
|
{
|
||||||
|
box_.set_name("taskbar");
|
||||||
|
if (!id.empty()) {
|
||||||
|
box_.get_style_context()->add_class(id);
|
||||||
|
}
|
||||||
|
event_box_.add(box_);
|
||||||
|
|
||||||
|
struct wl_display *display = Client::inst()->wl_display;
|
||||||
|
struct wl_registry *registry = wl_display_get_registry(display);
|
||||||
|
|
||||||
|
wl_registry_add_listener(registry, ®istry_listener_impl, this);
|
||||||
|
wl_display_roundtrip(display);
|
||||||
|
|
||||||
|
if (!manager_) {
|
||||||
|
spdlog::error("Failed to register as toplevel manager");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!seat_) {
|
||||||
|
spdlog::error("Failed to get wayland seat");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the configured icon theme if specified */
|
||||||
|
if (config_["icon-theme"].isString()) {
|
||||||
|
icon_theme_ = Gtk::IconTheme::create();
|
||||||
|
icon_theme_->set_custom_theme(config_["icon-theme"].asString());
|
||||||
|
spdlog::debug("Use custom icon theme: {}.", config_["icon-theme"].asString());
|
||||||
|
} else {
|
||||||
|
spdlog::debug("Use system default icon theme");
|
||||||
|
icon_theme_ = Gtk::IconTheme::get_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Taskbar::~Taskbar()
|
||||||
|
{
|
||||||
|
if (manager_) {
|
||||||
|
zwlr_foreign_toplevel_manager_v1_destroy(manager_);
|
||||||
|
manager_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::update()
|
||||||
|
{
|
||||||
|
for (auto& t : tasks_) {
|
||||||
|
t->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
AModule::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tm_handle_toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager,
|
||||||
|
struct zwlr_foreign_toplevel_handle_v1 *tl_handle)
|
||||||
|
{
|
||||||
|
return static_cast<Taskbar*>(data)->handle_toplevel_create(tl_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tm_handle_finished(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager)
|
||||||
|
{
|
||||||
|
return static_cast<Taskbar*>(data)->handle_finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl = {
|
||||||
|
.toplevel = tm_handle_toplevel,
|
||||||
|
.finished = tm_handle_finished,
|
||||||
|
};
|
||||||
|
|
||||||
|
void Taskbar::register_manager(struct wl_registry *registry, uint32_t name, uint32_t version)
|
||||||
|
{
|
||||||
|
if (manager_) {
|
||||||
|
spdlog::warn("Register foreign toplevel manager again although already existing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (version != 2) {
|
||||||
|
spdlog::warn("Using different foreign toplevel manager protocol version: {}", version);
|
||||||
|
}
|
||||||
|
|
||||||
|
manager_ = static_cast<struct zwlr_foreign_toplevel_manager_v1 *>(wl_registry_bind(registry, name,
|
||||||
|
&zwlr_foreign_toplevel_manager_v1_interface, version));
|
||||||
|
|
||||||
|
if (manager_)
|
||||||
|
zwlr_foreign_toplevel_manager_v1_add_listener(manager_, &toplevel_manager_impl, this);
|
||||||
|
else
|
||||||
|
spdlog::debug("Failed to register manager");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::register_seat(struct wl_registry *registry, uint32_t name, uint32_t version)
|
||||||
|
{
|
||||||
|
if (seat_) {
|
||||||
|
spdlog::warn("Register seat again although already existing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
seat_ = static_cast<wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1 *tl_handle)
|
||||||
|
{
|
||||||
|
tasks_.push_back(std::make_unique<Task>(bar_, config_, this, tl_handle, seat_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::handle_finished()
|
||||||
|
{
|
||||||
|
zwlr_foreign_toplevel_manager_v1_destroy(manager_);
|
||||||
|
manager_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::add_button(Gtk::Button &bt)
|
||||||
|
{
|
||||||
|
box_.pack_start(bt, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::move_button(Gtk::Button &bt, int pos)
|
||||||
|
{
|
||||||
|
box_.reorder_child(bt, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::remove_button(Gtk::Button &bt)
|
||||||
|
{
|
||||||
|
box_.remove(bt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Taskbar::remove_task(uint32_t id)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(std::begin(tasks_), std::end(tasks_),
|
||||||
|
[id](const TaskPtr &p) { return p->id() == id; });
|
||||||
|
|
||||||
|
if (it == std::end(tasks_)) {
|
||||||
|
spdlog::warn("Can't find task with id {}", id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks_.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Taskbar::show_output(struct wl_output *output) const
|
||||||
|
{
|
||||||
|
return output == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Taskbar::all_outputs() const
|
||||||
|
{
|
||||||
|
static bool result = config_["all_outputs"].isBool() ? config_["all_outputs"].asBool() : false;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Glib::RefPtr<Gtk::IconTheme> Taskbar::icon_theme() const
|
||||||
|
{
|
||||||
|
return icon_theme_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace waybar::modules::wlr */
|
Loading…
Reference in New Issue
Block a user