feat(plugins): add API to list clients, their focused panes and running commands/plugins (#3687)

* fix(list-clients): properly show client info after a tab was closed

* feat(plugins): add API to list clients, their focused panes and running commands/plugins

* style(fmt): rustfmt
This commit is contained in:
Aram Drevekenin 2024-10-22 15:27:40 +02:00 committed by GitHub
parent 912c9f599f
commit d671ab650e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 339 additions and 16 deletions

View file

@ -525,6 +525,9 @@ impl ZellijPlugin for State {
];
rebind_keys(keys_to_unbind, keys_to_rebind, write_to_disk);
},
BareKey::Char('z') if key.has_modifiers(&[KeyModifier::Alt]) => {
list_clients();
},
_ => {},
},
Event::CustomMessage(message, payload) => {

View file

@ -26,7 +26,7 @@ use wasm_bridge::WasmBridge;
use zellij_utils::{
async_std::{channel, future::timeout, task},
data::{
Event, EventType, InputMode, MessageToPlugin, PermissionStatus, PermissionType,
ClientInfo, Event, EventType, InputMode, MessageToPlugin, PermissionStatus, PermissionType,
PipeMessage, PipeSource, PluginCapabilities,
},
errors::{prelude::*, ContextType, PluginContext},
@ -158,6 +158,7 @@ pub enum PluginInstruction {
file_path: Option<PathBuf>,
},
WatchFilesystem,
ListClientsToPlugin(SessionLayoutMetadata, PluginId, ClientId),
Exit,
}
@ -203,6 +204,7 @@ impl From<&PluginInstruction> for PluginContext {
PluginInstruction::FailedToWriteConfigToDisk { .. } => {
PluginContext::FailedToWriteConfigToDisk
},
PluginInstruction::ListClientsToPlugin(..) => PluginContext::ListClientsToPlugin,
}
}
}
@ -607,6 +609,35 @@ pub(crate) fn plugin_thread_main(
},
}
},
PluginInstruction::ListClientsToPlugin(
mut session_layout_metadata,
plugin_id,
client_id,
) => {
populate_session_layout_metadata(
&mut session_layout_metadata,
&wasm_bridge,
&plugin_aliases,
);
let mut clients_metadata = session_layout_metadata.all_clients_metadata();
let mut client_list_for_plugin = vec![];
let default_editor = session_layout_metadata.default_editor.clone();
for (client_metadata_id, client_metadata) in clients_metadata.iter_mut() {
let is_current_client = client_metadata_id == &client_id;
client_list_for_plugin.push(ClientInfo::new(
*client_metadata_id,
client_metadata.get_pane_id().into(),
client_metadata.stringify_command(&default_editor),
is_current_client,
));
}
let updates = vec![(
Some(plugin_id),
Some(client_id),
Event::ListClients(client_list_for_plugin),
)];
wasm_bridge.update_plugins(updates, shutdown_send.clone())?;
},
PluginInstruction::LogLayoutToHd(mut session_layout_metadata) => {
populate_session_layout_metadata(
&mut session_layout_metadata,

View file

@ -8551,3 +8551,74 @@ pub fn rebind_keys_plugin_command() {
.clone();
assert_snapshot!(format!("{:#?}", rebind_event));
}
#[test]
#[ignore]
pub fn list_clients_plugin_command() {
let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
// destructor removes the directory
let plugin_host_folder = PathBuf::from(temp_folder.path());
let cache_path = plugin_host_folder.join("permissions_test.kdl");
let (plugin_thread_sender, screen_receiver, teardown) =
create_plugin_thread(Some(plugin_host_folder));
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
..Default::default()
});
let tab_index = 1;
let client_id = 1;
let size = Size {
cols: 121,
rows: 20,
};
let received_screen_instructions = Arc::new(Mutex::new(vec![]));
let screen_thread = grant_permissions_and_log_actions_in_thread!(
received_screen_instructions,
ScreenInstruction::ListClientsToPlugin,
screen_receiver,
1,
&PermissionType::ChangeApplicationState,
cache_path,
plugin_thread_sender,
client_id
);
let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
let _ = plugin_thread_sender.send(PluginInstruction::Load(
plugin_should_float,
false,
plugin_title,
run_plugin,
Some(tab_index),
None,
client_id,
size,
None,
false,
));
std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
Event::Key(KeyWithModifier::new(BareKey::Char('z')).with_alt_modifier()), // this triggers the enent in the fixture plugin
)]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown();
let list_clients_instruction = received_screen_instructions
.lock()
.unwrap()
.iter()
.find_map(|i| {
if let ScreenInstruction::ListClientsToPlugin(..) = i {
Some(i.clone())
} else {
None
}
})
.unwrap();
assert_snapshot!(format!("{:#?}", list_clients_instruction));
}

View file

@ -0,0 +1,9 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 8623
expression: "format!(\"{:#?}\", list_clients_instruction)"
---
ListClientsToPlugin(
0,
1,
)

View file

@ -351,6 +351,7 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
keys_to_unbind,
write_config_to_disk,
} => rebind_keys(env, keys_to_rebind, keys_to_unbind, write_config_to_disk)?,
PluginCommand::ListClients => list_clients(env),
},
(PermissionStatus::Denied, permission) => {
log::error!(
@ -1474,6 +1475,15 @@ fn dump_session_layout(env: &PluginEnv) {
.map(|sender| sender.send(ScreenInstruction::DumpLayoutToPlugin(env.plugin_id)));
}
fn list_clients(env: &PluginEnv) {
let _ = env.senders.to_screen.as_ref().map(|sender| {
sender.send(ScreenInstruction::ListClientsToPlugin(
env.plugin_id,
env.client_id,
))
});
}
fn scan_host_folder(env: &PluginEnv, folder_to_scan: PathBuf) {
if !folder_to_scan.starts_with("/host") {
log::error!(
@ -1897,7 +1907,9 @@ fn check_command_permission(
| PluginCommand::BlockCliPipeInput(..)
| PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes,
PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins,
PluginCommand::DumpSessionLayout => PermissionType::ReadApplicationState,
PluginCommand::ListClients | PluginCommand::DumpSessionLayout => {
PermissionType::ReadApplicationState
},
PluginCommand::RebindKeys { .. } | PluginCommand::Reconfigure(..) => {
PermissionType::Reconfigure
},

View file

@ -101,6 +101,7 @@ pub enum PtyInstruction {
client_id: ClientId,
default_editor: Option<PathBuf>,
},
ListClientsToPlugin(SessionLayoutMetadata, PluginId, ClientId),
Exit,
}
@ -125,6 +126,7 @@ impl From<&PtyInstruction> for PtyContext {
PtyInstruction::FillPluginCwd(..) => PtyContext::FillPluginCwd,
PtyInstruction::ListClientsMetadata(..) => PtyContext::ListClientsMetadata,
PtyInstruction::Reconfigure { .. } => PtyContext::Reconfigure,
PtyInstruction::ListClientsToPlugin(..) => PtyContext::ListClientsToPlugin,
PtyInstruction::Exit => PtyContext::Exit,
}
}
@ -724,6 +726,23 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
.with_context(err_context)
.non_fatal();
},
PtyInstruction::ListClientsToPlugin(
mut session_layout_metadata,
plugin_id,
client_id,
) => {
let err_context = || format!("Failed to dump layout");
pty.populate_session_layout_metadata(&mut session_layout_metadata);
pty.bus
.senders
.send_to_plugin(PluginInstruction::ListClientsToPlugin(
session_layout_metadata,
plugin_id,
client_id,
))
.with_context(err_context)
.non_fatal();
},
PtyInstruction::LogLayoutToHd(mut session_layout_metadata) => {
let err_context = || format!("Failed to dump layout");
pty.populate_session_layout_metadata(&mut session_layout_metadata);

View file

@ -410,6 +410,7 @@ pub enum ScreenInstruction {
should_change_focus_to_new_tab: bool,
client_id: ClientId,
},
ListClientsToPlugin(PluginId, ClientId),
}
impl From<&ScreenInstruction> for ScreenContext {
@ -621,6 +622,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::BreakPanesToTabWithIndex { .. } => {
ScreenContext::BreakPanesToTabWithIndex
},
ScreenInstruction::ListClientsToPlugin(..) => ScreenContext::ListClientsToPlugin,
}
}
}
@ -2506,7 +2508,7 @@ impl Screen {
let active_tab_index =
first_client_id.and_then(|client_id| self.active_tab_indices.get(&client_id));
for (tab_index, tab) in self.tabs.values().enumerate() {
for (tab_index, tab) in self.tabs.iter() {
let tab_is_focused = active_tab_index == Some(&tab_index);
let hide_floating_panes = !tab.are_floating_panes_visible();
let mut suppressed_panes = HashMap::new();
@ -3110,6 +3112,21 @@ pub(crate) fn screen_thread_main(
.with_context(err_context)
.non_fatal();
},
ScreenInstruction::ListClientsToPlugin(plugin_id, client_id) => {
let err_context = || format!("Failed to dump layout");
let session_layout_metadata =
screen.get_layout_metadata(screen.default_shell.clone());
screen
.bus
.senders
.send_to_pty(PtyInstruction::ListClientsToPlugin(
session_layout_metadata,
plugin_id,
client_id,
))
.with_context(err_context)
.non_fatal();
},
ScreenInstruction::EditScrollback(client_id) => {
active_tab_and_connected_client_id!(
screen,

View file

@ -82,6 +82,28 @@ impl SessionLayoutMetadata {
ClientMetadata::render_many(clients_metadata, &self.default_editor)
}
pub fn all_clients_metadata(&self) -> BTreeMap<ClientId, ClientMetadata> {
let mut clients_metadata: BTreeMap<ClientId, ClientMetadata> = BTreeMap::new();
for tab in &self.tabs {
let panes = if tab.hide_floating_panes {
&tab.tiled_panes
} else {
&tab.floating_panes
};
for pane in panes {
for focused_client in &pane.focused_clients {
clients_metadata.insert(
*focused_client,
ClientMetadata {
pane_id: pane.id.clone(),
command: pane.run.clone(),
},
);
}
}
}
clients_metadata
}
pub fn is_dirty(&self) -> bool {
// here we check to see if the serialized layout would be different than the base one, and
// thus is "dirty". A layout is considered dirty if one of the following is true:
@ -372,7 +394,7 @@ impl PaneLayoutMetadata {
}
}
struct ClientMetadata {
pub struct ClientMetadata {
pane_id: PaneId,
command: Option<Run>,
}
@ -404,6 +426,9 @@ impl ClientMetadata {
};
stringified.unwrap_or("N/A".to_owned())
}
pub fn get_pane_id(&self) -> PaneId {
self.pane_id
}
pub fn render_many(
clients_metadata: BTreeMap<ClientId, ClientMetadata>,
default_editor: &Option<PathBuf>,

View file

@ -854,6 +854,15 @@ pub fn dump_session_layout() {
unsafe { host_run_plugin_command() };
}
/// Get a list of clients, their focused pane and running command or focused plugin back as an
/// Event::ListClients (note: this event must be subscribed to)
pub fn list_clients() {
let plugin_command = PluginCommand::ListClients;
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
/// Change configuration for the current user
pub fn reconfigure(new_config: String, save_configuration_file: bool) {
let plugin_command = PluginCommand::Reconfigure(new_config, save_configuration_file);

View file

@ -11,7 +11,7 @@ pub struct Event {
pub name: i32,
#[prost(
oneof = "event::Payload",
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22"
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23"
)]
pub payload: ::core::option::Option<event::Payload>,
}
@ -62,10 +62,30 @@ pub mod event {
CommandPaneRerunPayload(super::CommandPaneReRunPayload),
#[prost(message, tag = "22")]
FailedToWriteConfigToDiskPayload(super::FailedToWriteConfigToDiskPayload),
#[prost(message, tag = "23")]
ListClientsPayload(super::ListClientsPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ListClientsPayload {
#[prost(message, repeated, tag = "1")]
pub client_info: ::prost::alloc::vec::Vec<ClientInfo>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ClientInfo {
#[prost(uint32, tag = "1")]
pub client_id: u32,
#[prost(message, optional, tag = "2")]
pub pane_id: ::core::option::Option<PaneId>,
#[prost(string, tag = "3")]
pub running_command: ::prost::alloc::string::String,
#[prost(bool, tag = "4")]
pub is_current_client: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FailedToWriteConfigToDiskPayload {
#[prost(string, optional, tag = "1")]
pub file_path: ::core::option::Option<::prost::alloc::string::String>,
@ -447,6 +467,7 @@ pub enum EventType {
EditPaneExited = 23,
CommandPaneReRun = 24,
FailedToWriteConfigToDisk = 25,
ListClients = 26,
}
impl EventType {
/// String value of the enum field names used in the ProtoBuf definition.
@ -481,6 +502,7 @@ impl EventType {
EventType::EditPaneExited => "EditPaneExited",
EventType::CommandPaneReRun => "CommandPaneReRun",
EventType::FailedToWriteConfigToDisk => "FailedToWriteConfigToDisk",
EventType::ListClients => "ListClients",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -512,6 +534,7 @@ impl EventType {
"EditPaneExited" => Some(Self::EditPaneExited),
"CommandPaneReRun" => Some(Self::CommandPaneReRun),
"FailedToWriteConfigToDisk" => Some(Self::FailedToWriteConfigToDisk),
"ListClients" => Some(Self::ListClients),
_ => None,
}
}

View file

@ -715,6 +715,7 @@ pub enum CommandName {
ReloadPlugin = 110,
LoadNewPlugin = 111,
RebindKeys = 112,
ListClients = 113,
}
impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition.
@ -838,6 +839,7 @@ impl CommandName {
CommandName::ReloadPlugin => "ReloadPlugin",
CommandName::LoadNewPlugin => "LoadNewPlugin",
CommandName::RebindKeys => "RebindKeys",
CommandName::ListClients => "ListClients",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -958,6 +960,7 @@ impl CommandName {
"ReloadPlugin" => Some(Self::ReloadPlugin),
"LoadNewPlugin" => Some(Self::LoadNewPlugin),
"RebindKeys" => Some(Self::RebindKeys),
"ListClients" => Some(Self::ListClients),
_ => None,
}
}

View file

@ -921,6 +921,7 @@ pub enum Event {
EditPaneExited(u32, Option<i32>, Context), // u32 - terminal_pane_id, Option<i32> - exit code
CommandPaneReRun(u32, Context), // u32 - terminal_pane_id, Option<i32> -
FailedToWriteConfigToDisk(Option<String>), // String -> the file path we failed to write
ListClients(Vec<ClientInfo>),
}
#[derive(
@ -1365,6 +1366,29 @@ pub struct PaneInfo {
/// (eg. the default `status-bar` or `tab-bar`).
pub is_selectable: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct ClientInfo {
pub client_id: ClientId,
pub pane_id: PaneId,
pub running_command: String,
pub is_current_client: bool,
}
impl ClientInfo {
pub fn new(
client_id: ClientId,
pane_id: PaneId,
running_command: String,
is_current_client: bool,
) -> Self {
ClientInfo {
client_id,
pane_id,
running_command,
is_current_client,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct PluginIds {
@ -1878,4 +1902,5 @@ pub enum PluginCommand {
keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
write_config_to_disk: bool,
},
ListClients,
}

View file

@ -372,6 +372,7 @@ pub enum ScreenContext {
CloseTabWithIndex,
BreakPanesToNewTab,
BreakPanesToTabWithIndex,
ListClientsToPlugin,
}
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
@ -395,6 +396,7 @@ pub enum PtyContext {
DumpLayoutToPlugin,
ListClientsMetadata,
Reconfigure,
ListClientsToPlugin,
Exit,
}
@ -432,6 +434,7 @@ pub enum PluginContext {
ListClientsMetadata,
Reconfigure,
FailedToWriteConfigToDisk,
ListClientsToPlugin,
}
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.

View file

@ -49,6 +49,7 @@ enum EventType {
EditPaneExited = 23;
CommandPaneReRun = 24;
FailedToWriteConfigToDisk = 25;
ListClients = 26;
}
message EventNameList {
@ -79,9 +80,21 @@ message Event {
EditPaneExitedPayload edit_pane_exited_payload = 20;
CommandPaneReRunPayload command_pane_rerun_payload = 21;
FailedToWriteConfigToDiskPayload failed_to_write_config_to_disk_payload = 22;
ListClientsPayload list_clients_payload = 23;
}
}
message ListClientsPayload {
repeated ClientInfo client_info = 1;
}
message ClientInfo {
uint32 client_id = 1;
PaneId pane_id = 2;
string running_command = 3;
bool is_current_client = 4;
}
message FailedToWriteConfigToDiskPayload {
optional string file_path = 1;
}

View file

@ -1,14 +1,15 @@
pub use super::generated_api::api::{
action::{Action as ProtobufAction, Position as ProtobufPosition},
event::{
event::Payload as ProtobufEventPayload, CopyDestination as ProtobufCopyDestination,
Event as ProtobufEvent, EventNameList as ProtobufEventNameList,
EventType as ProtobufEventType, FileMetadata as ProtobufFileMetadata,
InputModeKeybinds as ProtobufInputModeKeybinds, KeyBind as ProtobufKeyBind,
LayoutInfo as ProtobufLayoutInfo, ModeUpdatePayload as ProtobufModeUpdatePayload,
PaneId as ProtobufPaneId, PaneInfo as ProtobufPaneInfo,
PaneManifest as ProtobufPaneManifest, PaneType as ProtobufPaneType,
PluginInfo as ProtobufPluginInfo, ResurrectableSession as ProtobufResurrectableSession,
event::Payload as ProtobufEventPayload, ClientInfo as ProtobufClientInfo,
CopyDestination as ProtobufCopyDestination, Event as ProtobufEvent,
EventNameList as ProtobufEventNameList, EventType as ProtobufEventType,
FileMetadata as ProtobufFileMetadata, InputModeKeybinds as ProtobufInputModeKeybinds,
KeyBind as ProtobufKeyBind, LayoutInfo as ProtobufLayoutInfo,
ModeUpdatePayload as ProtobufModeUpdatePayload, PaneId as ProtobufPaneId,
PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest,
PaneType as ProtobufPaneType, PluginInfo as ProtobufPluginInfo,
ResurrectableSession as ProtobufResurrectableSession,
SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, *,
},
input_mode::InputMode as ProtobufInputMode,
@ -17,9 +18,9 @@ pub use super::generated_api::api::{
};
#[allow(hidden_glob_reexports)]
use crate::data::{
CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier, LayoutInfo,
ModeInfo, Mouse, PaneId, PaneInfo, PaneManifest, PermissionStatus, PluginCapabilities,
PluginInfo, SessionInfo, Style, TabInfo,
ClientInfo, CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier,
LayoutInfo, ModeInfo, Mouse, PaneId, PaneInfo, PaneManifest, PermissionStatus,
PluginCapabilities, PluginInfo, SessionInfo, Style, TabInfo,
};
use crate::errors::prelude::*;
@ -320,11 +321,50 @@ impl TryFrom<ProtobufEvent> for Event {
)),
_ => Err("Malformed payload for the FailedToWriteConfigToDisk Event"),
},
Some(ProtobufEventType::ListClients) => match protobuf_event.payload {
Some(ProtobufEventPayload::ListClientsPayload(mut list_clients_payload)) => {
Ok(Event::ListClients(
list_clients_payload
.client_info
.drain(..)
.filter_map(|c| c.try_into().ok())
.collect(),
))
},
_ => Err("Malformed payload for the FailedToWriteConfigToDisk Event"),
},
None => Err("Unknown Protobuf Event"),
}
}
}
impl TryFrom<ProtobufClientInfo> for ClientInfo {
type Error = &'static str;
fn try_from(protobuf_client_info: ProtobufClientInfo) -> Result<Self, &'static str> {
Ok(ClientInfo::new(
protobuf_client_info.client_id as u16,
protobuf_client_info
.pane_id
.ok_or("No pane id found")?
.try_into()?,
protobuf_client_info.running_command,
protobuf_client_info.is_current_client,
))
}
}
impl TryFrom<ClientInfo> for ProtobufClientInfo {
type Error = &'static str;
fn try_from(client_info: ClientInfo) -> Result<Self, &'static str> {
Ok(ProtobufClientInfo {
client_id: client_info.client_id as u32,
pane_id: Some(client_info.pane_id.try_into()?),
running_command: client_info.running_command,
is_current_client: client_info.is_current_client,
})
}
}
impl TryFrom<Event> for ProtobufEvent {
type Error = &'static str;
fn try_from(event: Event) -> Result<Self, &'static str> {
@ -634,6 +674,15 @@ impl TryFrom<Event> for ProtobufEvent {
FailedToWriteConfigToDiskPayload { file_path },
)),
}),
Event::ListClients(mut client_info_list) => Ok(ProtobufEvent {
name: ProtobufEventType::ListClients as i32,
payload: Some(event::Payload::ListClientsPayload(ListClientsPayload {
client_info: client_info_list
.drain(..)
.filter_map(|c| c.try_into().ok())
.collect(),
})),
}),
}
}
}
@ -1178,6 +1227,7 @@ impl TryFrom<ProtobufEventType> for EventType {
ProtobufEventType::EditPaneExited => EventType::EditPaneExited,
ProtobufEventType::CommandPaneReRun => EventType::CommandPaneReRun,
ProtobufEventType::FailedToWriteConfigToDisk => EventType::FailedToWriteConfigToDisk,
ProtobufEventType::ListClients => EventType::ListClients,
})
}
}
@ -1212,6 +1262,7 @@ impl TryFrom<EventType> for ProtobufEventType {
EventType::EditPaneExited => ProtobufEventType::EditPaneExited,
EventType::CommandPaneReRun => ProtobufEventType::CommandPaneReRun,
EventType::FailedToWriteConfigToDisk => ProtobufEventType::FailedToWriteConfigToDisk,
EventType::ListClients => ProtobufEventType::ListClients,
})
}
}

View file

@ -126,6 +126,7 @@ enum CommandName {
ReloadPlugin = 110;
LoadNewPlugin = 111;
RebindKeys = 112;
ListClients = 113;
}
message PluginCommand {

View file

@ -1299,6 +1299,10 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for RebindKeys"),
},
Some(CommandName::ListClients) => match protobuf_plugin_command.payload {
Some(_) => Err("ListClients should have no payload, found a payload"),
None => Ok(PluginCommand::ListClients),
},
None => Err("Unrecognized plugin command"),
}
}
@ -2122,6 +2126,10 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
write_config_to_disk,
})),
}),
PluginCommand::ListClients => Ok(ProtobufPluginCommand {
name: CommandName::ListClients as i32,
payload: None,
}),
}
}
}