mirror of
https://github.com/ErikReider/SwayNotificationCenter.git
synced 2024-11-22 09:43:52 +03:00
Add ControllsWidget (#203)
This commit is contained in:
parent
68785053f2
commit
5779017397
@ -49,6 +49,8 @@ These widgets can be customized, added, removed and even reordered
|
||||
- Notifications (Will always be visible)
|
||||
- Label
|
||||
- Mpris (Media player controls for Spotify, Firefox, Chrome, etc...)
|
||||
- Menubar with dropdown and buttons
|
||||
- Button grid
|
||||
|
||||
## Planned Features
|
||||
|
||||
|
119
man/swaync.5.scd
119
man/swaync.5.scd
@ -191,6 +191,10 @@ config file to be able to detect config errors
|
||||
optional: true ++
|
||||
*mpris*++
|
||||
optional: true ++
|
||||
*menubar*++
|
||||
optional: true ++
|
||||
*buttons-grid*++
|
||||
optional: true ++
|
||||
description: ++
|
||||
Which order and which widgets to display. ++
|
||||
If the \"notifications\" widget isn't specified, it ++
|
||||
@ -280,6 +284,89 @@ config file to be able to detect config errors
|
||||
default: 12 ++
|
||||
description: The border radius of the album art. ++
|
||||
description: A widget that displays multiple music players. ++
|
||||
*menubar*++
|
||||
type: object ++
|
||||
css classes: ++
|
||||
widget-menubar ++
|
||||
.widget-menubar>box>.menu-button-bar ++
|
||||
name of element given after menu or buttons with # ++
|
||||
patternProperties: ++
|
||||
menu#<name>: ++
|
||||
type: object ++
|
||||
properties: ++
|
||||
label: ++
|
||||
type: string ++
|
||||
optional: true ++
|
||||
default: "Menu" ++
|
||||
description: Label of button to show/hide menu dropdown ++
|
||||
position: ++
|
||||
type: string ++
|
||||
optional: true ++
|
||||
default: "right" ++
|
||||
description: Horizontal position of the button in the bar ++
|
||||
enum: ["right", "left"] ++
|
||||
actions: ++
|
||||
type: array ++
|
||||
Default values: [] ++
|
||||
Valid array values: ++
|
||||
type: object ++
|
||||
properties: ++
|
||||
label: ++
|
||||
type: string ++
|
||||
default: "label" ++
|
||||
description: Text to be displayed in button ++
|
||||
command: ++
|
||||
type: string ++
|
||||
default: "" ++
|
||||
description: "Command to be executed on click" ++
|
||||
description: A list of actions containing a label and a command ++
|
||||
description: A button to reveal a dropdown with action-buttons ++
|
||||
buttons#<name>: ++
|
||||
type: object ++
|
||||
properties: ++
|
||||
position: ++
|
||||
type: string ++
|
||||
optional: true ++
|
||||
default: "right" ++
|
||||
description: Horizontal position of the buttons in the bar ++
|
||||
enum: ["right", "left"] ++
|
||||
actions: ++
|
||||
type: array ++
|
||||
Default values: [] ++
|
||||
Valid array values: ++
|
||||
type: object ++
|
||||
properties: ++
|
||||
label: ++
|
||||
type: string ++
|
||||
default: "label" ++
|
||||
description: Text to be displayed in button ++
|
||||
command: ++
|
||||
type: string ++
|
||||
default: "" ++
|
||||
description: "Command to be executed on click" ++
|
||||
description: A list of actions containing a label and a command ++
|
||||
description: A list of buttons to be displayed in the menu-button-bar ++
|
||||
*buttons-grid*++
|
||||
type: object ++
|
||||
css class: widget-buttons (access buttons with >flowbox>flowboxchild>button) ++
|
||||
properties: ++
|
||||
actions: ++
|
||||
type: array ++
|
||||
Default values: [] ++
|
||||
Valid array values: ++
|
||||
type: object ++
|
||||
properties: ++
|
||||
label: ++
|
||||
type: string ++
|
||||
default: "label" ++
|
||||
description: Text to be displayed in button ++
|
||||
command: ++
|
||||
type: string ++
|
||||
default: "" ++
|
||||
description: "Command to be executed on click" ++
|
||||
description: A list of actions containing a label and a command ++
|
||||
description: A grid of buttons that execute shell commands ++
|
||||
|
||||
example:
|
||||
```
|
||||
{
|
||||
@ -299,6 +386,38 @@ config file to be able to detect config errors
|
||||
"mpris": {
|
||||
"image-size": 96,
|
||||
"image-radius": 12
|
||||
},
|
||||
"menubar": {
|
||||
"menu#power": {
|
||||
"label": "Power",
|
||||
"position": "right",
|
||||
"actions": [
|
||||
{
|
||||
"label": "Shut down",
|
||||
"command": "systemctl poweroff"
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
"buttons#screenshot": {
|
||||
"position": "left",
|
||||
"actions": [
|
||||
{
|
||||
"label": "Screenshot",
|
||||
"command": "grim"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"actions": [
|
||||
{
|
||||
"label": "wifi",
|
||||
"command": "rofi-wifi-menu"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -266,6 +266,12 @@
|
||||
},
|
||||
"^mpris(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$ref": "#/widgets/mpris"
|
||||
},
|
||||
"^buttons-grid(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$ref": "#/widgets/buttons"
|
||||
},
|
||||
"^menubar(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$ref": "#/widgets/menubar"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,6 +345,76 @@
|
||||
"default": 12
|
||||
}
|
||||
}
|
||||
},
|
||||
"buttons-grid": {
|
||||
"type": "object",
|
||||
"description": "A widget to add a grid of buttons that execute shell commands",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"description": "A list of actions containing a label and a command",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Text to be displayed in button",
|
||||
"default": "label"
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "Command to be executed on click",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"menubar": {
|
||||
"type": "object",
|
||||
"description": "A bar that contains action-buttons and buttons to open a dropdown with action-buttons",
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {
|
||||
"^menu(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"type": "object",
|
||||
"description": "A button that opens a dropdown with action-buttons",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Text to be displayed in button",
|
||||
"default": "Menu"
|
||||
},
|
||||
"position": {
|
||||
"type": "string",
|
||||
"description": "Horizontal position of the button in the bar",
|
||||
"default": "right",
|
||||
"enum": ["right", "left"]
|
||||
},
|
||||
"actions": {
|
||||
"$ref" : "#/widgets/buttons-grid/properties/actions"
|
||||
}
|
||||
}
|
||||
},
|
||||
"^buttons(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"type": "object",
|
||||
"description": "A list of action-buttons to be displayed in the topbar",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"position": {
|
||||
"type": "string",
|
||||
"description": "Horizontal position of the button in the bar",
|
||||
"default": "right",
|
||||
"enum": ["right", "left"]
|
||||
},
|
||||
"actions": {
|
||||
"$ref" : "#/widgets/buttons-grid/properties/actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
using Posix;
|
||||
|
||||
namespace SwayNotificationCenter.Widgets {
|
||||
public abstract class BaseWidget : Gtk.Box {
|
||||
public abstract string widget_name { get; }
|
||||
@ -70,5 +72,43 @@ namespace SwayNotificationCenter.Widgets {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected Json.Array ? get_prop_array (Json.Object config, string value_key) {
|
||||
if (!config.has_member (value_key)) {
|
||||
debug ("%s: Config doesn't have key: %s!\n", key, value_key);
|
||||
return null;
|
||||
}
|
||||
var member = config.get_member (value_key);
|
||||
if (member.get_node_type () != Json.NodeType.ARRAY) {
|
||||
debug ("Unable to find Json Array for member %s", value_key);
|
||||
}
|
||||
return config.get_array_member (value_key);
|
||||
}
|
||||
|
||||
protected Action[] parse_actions (Json.Array actions) {
|
||||
Action[] res = new Action[actions.get_length ()];
|
||||
for (int i = 0; i < actions.get_length (); i++) {
|
||||
string label = actions.get_object_element (i).get_string_member_with_default ("label", "label");
|
||||
string command = actions.get_object_element (i).get_string_member_with_default ("command", "");
|
||||
res[i] = Action () {
|
||||
label = label,
|
||||
command = command
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
protected void execute_command (string cmd) {
|
||||
pid_t pid;
|
||||
int status;
|
||||
if ((pid = fork ()) < 0) {
|
||||
perror ("fork()");
|
||||
}
|
||||
if (pid == 0) { // Child process
|
||||
execl ("/bin/sh", "sh", "-c", cmd);
|
||||
exit (EXIT_FAILURE); // should not return from execl
|
||||
}
|
||||
waitpid (pid, out status, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
src/controlCenter/widgets/buttonsGrid/buttonsGrid.vala
Normal file
39
src/controlCenter/widgets/buttonsGrid/buttonsGrid.vala
Normal file
@ -0,0 +1,39 @@
|
||||
using GLib;
|
||||
|
||||
namespace SwayNotificationCenter.Widgets {
|
||||
|
||||
public class ButtonsGrid : BaseWidget {
|
||||
public override string widget_name {
|
||||
get {
|
||||
return "buttons-grid";
|
||||
}
|
||||
}
|
||||
|
||||
Action[] actions;
|
||||
|
||||
public ButtonsGrid (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||
base (suffix, swaync_daemon, noti_daemon);
|
||||
|
||||
Json.Object ? config = get_config (this);
|
||||
if (config != null) {
|
||||
Json.Array a = get_prop_array (config, "actions");
|
||||
if (a != null) actions = parse_actions (a);
|
||||
}
|
||||
|
||||
Gtk.FlowBox container = new Gtk.FlowBox ();
|
||||
container.set_selection_mode (Gtk.SelectionMode.NONE);
|
||||
pack_start (container, true, true, 0);
|
||||
|
||||
// add action to container
|
||||
foreach (var act in actions) {
|
||||
Gtk.Button b = new Gtk.Button.with_label (act.label);
|
||||
|
||||
b.clicked.connect (() => execute_command (act.command));
|
||||
|
||||
container.insert (b, -1);
|
||||
}
|
||||
|
||||
show_all ();
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,12 @@ namespace SwayNotificationCenter.Widgets {
|
||||
case "mpris":
|
||||
widget = new Mpris.Mpris (suffix, swaync_daemon, noti_daemon);
|
||||
break;
|
||||
case "menubar":
|
||||
widget = new Menubar (suffix, swaync_daemon, noti_daemon);
|
||||
break;
|
||||
case "buttons-grid":
|
||||
widget = new ButtonsGrid (suffix, swaync_daemon, noti_daemon);
|
||||
break;
|
||||
default:
|
||||
warning ("Could not find widget: \"%s\"!", key);
|
||||
return null;
|
||||
|
175
src/controlCenter/widgets/menubar/menubar.vala
Normal file
175
src/controlCenter/widgets/menubar/menubar.vala
Normal file
@ -0,0 +1,175 @@
|
||||
using GLib;
|
||||
|
||||
namespace SwayNotificationCenter.Widgets {
|
||||
|
||||
public enum MenuType {
|
||||
BUTTONS,
|
||||
MENU
|
||||
}
|
||||
|
||||
public enum Position {
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
||||
public struct ConfigObject {
|
||||
string ? name;
|
||||
MenuType ? type;
|
||||
string ? label;
|
||||
Position ? position;
|
||||
Action[] actions;
|
||||
Gtk.Box ? menu;
|
||||
}
|
||||
|
||||
public struct Action {
|
||||
string ? label;
|
||||
string ? command;
|
||||
}
|
||||
|
||||
public class Menubar : BaseWidget {
|
||||
public override string widget_name {
|
||||
get {
|
||||
return "menubar";
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Box menus_container;
|
||||
Gtk.Box topbar_container;
|
||||
|
||||
List<ConfigObject ?> menu_objects;
|
||||
|
||||
public Menubar (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||
base (suffix, swaync_daemon, noti_daemon);
|
||||
|
||||
Json.Object ? config = get_config (this);
|
||||
if (config != null) {
|
||||
parse_config_objects (config);
|
||||
}
|
||||
|
||||
menus_container = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
|
||||
|
||||
topbar_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
|
||||
topbar_container.get_style_context ().add_class ("menu-button-bar");
|
||||
|
||||
menus_container.add (topbar_container);
|
||||
|
||||
for (int i = 0; i < menu_objects.length (); i++) {
|
||||
unowned ConfigObject ? obj = menu_objects.nth_data (i);
|
||||
add_menu (ref obj);
|
||||
}
|
||||
|
||||
pack_start (menus_container, true, true, 0);
|
||||
show_all ();
|
||||
|
||||
foreach (var obj in menu_objects) {
|
||||
obj.menu ?.hide ();
|
||||
}
|
||||
}
|
||||
|
||||
void add_menu (ref unowned ConfigObject ? obj) {
|
||||
switch (obj.type) {
|
||||
case MenuType.BUTTONS:
|
||||
Gtk.Box container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
|
||||
container.get_style_context ().add_class (obj.name);
|
||||
|
||||
foreach (Action a in obj.actions) {
|
||||
Gtk.Button b = new Gtk.Button.with_label (a.label);
|
||||
|
||||
b.clicked.connect (() => execute_command (a.command));
|
||||
|
||||
container.add (b);
|
||||
}
|
||||
switch (obj.position) {
|
||||
case Position.LEFT:
|
||||
topbar_container.pack_start (container, false, false, 0);
|
||||
break;
|
||||
case Position.RIGHT:
|
||||
topbar_container.pack_end (container, false, false, 0);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MenuType.MENU:
|
||||
Gtk.Button show_button = new Gtk.Button.with_label (obj.label);
|
||||
|
||||
Gtk.Box menu = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
|
||||
menu.get_style_context ().add_class (obj.name);
|
||||
obj.menu = menu;
|
||||
|
||||
show_button.clicked.connect (() => {
|
||||
bool visible = !menu.visible;
|
||||
foreach (var o in menu_objects) {
|
||||
o.menu ?.set_visible (false);
|
||||
}
|
||||
menu.set_visible (visible);
|
||||
});
|
||||
|
||||
foreach (var a in obj.actions) {
|
||||
Gtk.Button b = new Gtk.Button.with_label (a.label);
|
||||
b.clicked.connect (() => execute_command (a.command));
|
||||
menu.pack_start (b, true, true, 0);
|
||||
}
|
||||
|
||||
switch (obj.position) {
|
||||
case Position.RIGHT:
|
||||
topbar_container.pack_end (show_button, false, false, 0);
|
||||
break;
|
||||
case Position.LEFT:
|
||||
topbar_container.pack_start (show_button, false, false, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
menus_container.add (menu);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void parse_config_objects (Json.Object config) {
|
||||
var elements = config.get_members ();
|
||||
|
||||
menu_objects = new List<ConfigObject ?> ();
|
||||
for (int i = 0; i < elements.length (); i++) {
|
||||
string e = elements.nth_data (i);
|
||||
Json.Object ? obj = config.get_object_member (e);
|
||||
|
||||
if (obj == null) continue;
|
||||
|
||||
string[] key = e.split ("#");
|
||||
string t = key[0];
|
||||
MenuType type = MenuType.BUTTONS;
|
||||
if (t == "buttons") type = MenuType.BUTTONS;
|
||||
else if (t == "menu") type = MenuType.MENU;
|
||||
else info ("Invalid type for menu-object - valid options: 'menu' || 'buttons' using default");
|
||||
|
||||
string name = key[1];
|
||||
|
||||
string ? p = get_prop<string> (obj, "position");
|
||||
Position pos;
|
||||
if (p != "left" && p != "right") {
|
||||
pos = Position.RIGHT;
|
||||
info ("No position for menu-object given using default");
|
||||
} else if (p == "right") pos = Position.RIGHT;
|
||||
else pos = Position.LEFT;
|
||||
|
||||
Json.Array ? actions = get_prop_array (obj, "actions");
|
||||
if (actions == null) {
|
||||
info ("Error parsing actions for menu-object");
|
||||
}
|
||||
|
||||
string ? label = get_prop<string> (obj, "label");
|
||||
if (label == null) {
|
||||
label = "Menu";
|
||||
info ("No label for menu-object given using default");
|
||||
}
|
||||
|
||||
Action[] actions_list = parse_actions (actions);
|
||||
menu_objects.append (ConfigObject () {
|
||||
name = name,
|
||||
type = type,
|
||||
label = label,
|
||||
position = pos,
|
||||
actions = actions_list,
|
||||
menu = null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -36,6 +36,10 @@ widget_sources = [
|
||||
'controlCenter/widgets/mpris/mpris.vala',
|
||||
'controlCenter/widgets/mpris/interfaces.vala',
|
||||
'controlCenter/widgets/mpris/mpris_player.vala',
|
||||
# Widget: Menubar
|
||||
'controlCenter/widgets/menubar/menubar.vala',
|
||||
# Widget: Buttons Grid
|
||||
'controlCenter/widgets/buttonsGrid/buttonsGrid.vala',
|
||||
]
|
||||
|
||||
app_sources = [
|
||||
@ -62,6 +66,7 @@ app_deps = [
|
||||
dependency('libhandy-1', version: '>= 1.2.3'),
|
||||
meson.get_compiler('c').find_library('gtk-layer-shell'),
|
||||
meson.get_compiler('c').find_library('m', required : true),
|
||||
meson.get_compiler('vala').find_library('posix'),
|
||||
]
|
||||
|
||||
# Checks if the user wants scripting enabled
|
||||
|
@ -239,3 +239,47 @@
|
||||
.widget-mpris-subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Buttons widget */
|
||||
.widget-buttons-grid {
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
background-color: @noti-bg;
|
||||
}
|
||||
|
||||
.widget-buttons-grid>flowbox>flowboxchild>button{
|
||||
background: @noti-bg;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.widget-buttons-grid>flowbox>flowboxchild>button:hover {
|
||||
background: @noti-bg-hover;
|
||||
}
|
||||
|
||||
/* Menubar widget */
|
||||
.widget-menubar>box>.menu-button-bar>button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* .AnyName { Name defined in config after #
|
||||
background-color: @noti-bg;
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.AnyName>button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.AnyName>button:hover {
|
||||
background-color: @noti-bg-hover;
|
||||
} */
|
||||
|
||||
.topbar-buttons>button { /* Name defined in config after # */
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
Loading…
Reference in New Issue
Block a user