Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions api/protobuf/pinnacle/input/v1/input.proto
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ message Bind {
oneof bind {
Keybind key = 6;
Mousebind mouse = 7;
Gesturebind gesture = 8;
}
}

Expand Down Expand Up @@ -93,6 +94,50 @@ message MousebindOnPressRequest {
uint32 bind_id = 1;
}

// Gesturebinds

enum GestureDirection {
DOWN = 0;
LEFT = 1;
RIGHT = 2;
UP = 3;
DOWN_AND_LEFT = 4;
DOWN_AND_RIGHT = 5;
UP_AND_LEFT = 6;
UP_AND_RIGHT = 7;
Comment thread
rustysec marked this conversation as resolved.
Outdated
}

enum GestureType {
HOLD = 0;
PINCH = 1;
SWIPE = 2;
}
Comment thread
rustysec marked this conversation as resolved.
Outdated

message Gesturebind {
GestureDirection direction = 1;
uint32 fingers = 2;
GestureType gesture_type = 3;
}

message GesturebindStreamRequest {
uint32 bind_id = 1;
}
message GesturebindStreamResponse {
Edge edge = 1;
}

message GesturebindRequest {
uint32 bind_id = 1;
}

message GesturebindOnBeginRequest {
uint32 bind_id = 1;
}

message GesturebindOnFinishRequest {
uint32 bind_id = 1;
}

///////

message GetBindInfosRequest {}
Expand Down Expand Up @@ -313,9 +358,13 @@ service InputService {

rpc KeybindStream(KeybindStreamRequest) returns (stream KeybindStreamResponse);
rpc MousebindStream(MousebindStreamRequest) returns (stream MousebindStreamResponse);
rpc GesturebindStream(GesturebindStreamRequest) returns (stream GesturebindStreamResponse);

rpc KeybindOnPress(KeybindOnPressRequest) returns (google.protobuf.Empty);
rpc MousebindOnPress(MousebindOnPressRequest) returns (google.protobuf.Empty);
rpc Gesturebind(GesturebindRequest) returns (google.protobuf.Empty);
rpc GesturebindOnBegin(GesturebindOnBeginRequest) returns (google.protobuf.Empty);
rpc GesturebindOnFinish(GesturebindOnFinishRequest) returns (google.protobuf.Empty);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are Gesturebind and GesturebindOnFinish for?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New RPCs should return a custom message so we don't need to make a breaking change in case we ever need to return data.


// Xkb

Expand Down
167 changes: 166 additions & 1 deletion api/rust/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use num_enum::{FromPrimitive, IntoPrimitive};
use pinnacle_api_defs::pinnacle::input::{
self,
v1::{
BindProperties, BindRequest, EnterBindLayerRequest, GetBindInfosRequest,
BindProperties, BindRequest, EnterBindLayerRequest, GesturebindOnBeginRequest,
GesturebindOnFinishRequest, GesturebindStreamRequest, GetBindInfosRequest,
KeybindOnPressRequest, KeybindStreamRequest, MousebindOnPressRequest,
MousebindStreamRequest, SetBindPropertiesRequest, SetRepeatRateRequest, SetXcursorRequest,
SetXkbConfigRequest, SetXkbKeymapRequest, SwitchXkbLayoutRequest,
Expand All @@ -30,6 +31,8 @@ pub mod libinput;

pub use xkbcommon::xkb::Keysym;

pub use pinnacle_api_defs::pinnacle::input::v1::{GestureDirection, GestureType};

/// A mouse button.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
#[repr(u32)]
Expand Down Expand Up @@ -176,6 +179,17 @@ impl BindLayer {
new_mousebind(mods, button, self).block_on_tokio()
}

/// Creates a gesturebind on this layer.
pub fn gesturebind(
&self,
mods: Mod,
direction: GestureDirection,
fingers: u32,
gesture_type: GestureType,
) -> Gesturebind {
new_gesturebind(mods, direction, fingers, gesture_type, self).block_on_tokio()
}

/// Enters this layer, causing only its binds to be in effect.
pub fn enter(&self) {
Client::input()
Expand Down Expand Up @@ -524,6 +538,145 @@ async fn new_mousebind_stream(
send
}

// Gesturebinds

type GesturebindCallback = (Box<dyn FnMut() + Send + 'static>, Edge);

/// A Gesturebind.
pub struct Gesturebind {
bind_id: u32,
callback_sender: Option<UnboundedSender<GesturebindCallback>>,
}

bind_impl!(Gesturebind);

/// Creates a gesturebind on the [`DEFAULT`][BindLayer::DEFAULT] bind layer.
pub fn gesturebind(
mods: Mod,
direction: GestureDirection,
fingers: u32,
gesture_type: GestureType,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the biggest fan of how direction gets ignored when gesture_type is pinch or hold. See overarching review comment for my thoughts.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should create enums for GestureDirection and GestureType instead of exposing the generated protobuf enums to the public API.

) -> Gesturebind {
BindLayer::DEFAULT.gesturebind(mods, direction, fingers, gesture_type)
}

impl Gesturebind {
/// Runs a closure whenever this mousebind is pressed.
pub fn on_begin<F: FnMut() + Send + 'static>(&mut self, on_begin: F) -> &mut Self {
let sender = self
.callback_sender
.get_or_insert_with(|| new_gesturebind_stream(self.bind_id).block_on_tokio());
let _ = sender.send((Box::new(on_begin), Edge::Press));

Client::input()
.gesturebind_on_begin(GesturebindOnBeginRequest {
bind_id: self.bind_id,
})
.block_on_tokio()
.unwrap();

self
}

/// Runs a closure whenever this mousebind is released.
pub fn on_finish<F: FnMut() + Send + 'static>(&mut self, on_finish: F) -> &mut Self {
let sender = self
.callback_sender
.get_or_insert_with(|| new_gesturebind_stream(self.bind_id).block_on_tokio());
let _ = sender.send((Box::new(on_finish), Edge::Release));

Client::input()
.gesturebind_on_finish(GesturebindOnFinishRequest {
bind_id: self.bind_id,
})
.block_on_tokio()
.unwrap();
Comment on lines +649 to +654

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keybind_on_press is used to let the compositor know when to suppress any release events associated with the bind. I can see you've added gesturebind_on_begin which I believe is supposed to do the same thing, but there's no reason for this gesturebind_on_finish RPC.


self
}
}

async fn new_gesturebind(
mods: Mod,
direction: GestureDirection,
fingers: u32,
gesture_type: GestureType,
layer: &BindLayer,
) -> Gesturebind {
let ignore_mods = mods.api_ignore_mods();
let mods = mods.api_mods();

let bind_id = Client::input()
.bind(BindRequest {
bind: Some(input::v1::Bind {
mods: mods.into_iter().map(|m| m.into()).collect(),
ignore_mods: ignore_mods.into_iter().map(|m| m.into()).collect(),
layer_name: layer.name.clone(),
properties: Some(BindProperties::default()),
bind: Some(input::v1::bind::Bind::Gesture(input::v1::Gesturebind {
direction: direction.into(),
fingers,
gesture_type: gesture_type.into(),
})),
}),
})
.await
.unwrap()
.into_inner()
.bind_id;

Gesturebind {
bind_id,
callback_sender: None,
}
}

async fn new_gesturebind_stream(
bind_id: u32,
) -> UnboundedSender<(Box<dyn FnMut() + Send + 'static>, Edge)> {
let mut from_server = Client::input()
.gesturebind_stream(GesturebindStreamRequest { bind_id })
.await
.unwrap()
.into_inner();

let (send, mut recv) = unbounded_channel();

tokio::spawn(async move {
let mut on_presses = Vec::<Box<dyn FnMut() + Send + 'static>>::new();
let mut on_releases = Vec::<Box<dyn FnMut() + Send + 'static>>::new();

loop {
tokio::select! {
Some(Ok(response)) = from_server.next() => {
match response.edge() {
input::v1::Edge::Unspecified => (),
input::v1::Edge::Press => {
for on_press in on_presses.iter_mut() {
on_press();
}
}
input::v1::Edge::Release => {
for on_release in on_releases.iter_mut() {
on_release();
}
}
}
}
Some((cb, edge)) = recv.recv() => {
match edge {
Edge::Press => on_presses.push(cb),
Edge::Release => on_releases.push(cb),
}
}
else => break,
}
}
});

send
}

/// A struct that lets you define xkeyboard config options.
///
/// See `xkeyboard-config(7)` for more information.
Expand Down Expand Up @@ -699,6 +852,13 @@ pub enum BindInfoKind {
/// Which mouse button this bind uses.
button: MouseButton,
},
/// This is a gesturebind.
Gesture {
/// Direction of the gesture.
direction: GestureDirection,
/// Fingers used in the gesture.
fingers: u32,
},
}

/// Sets the keyboard's repeat rate.
Expand Down Expand Up @@ -845,6 +1005,11 @@ pub fn bind_infos() -> impl Iterator<Item = BindInfo> {
input::v1::bind::Bind::Mouse(mousebind) => BindInfoKind::Mouse {
button: MouseButton::from(mousebind.button),
},
input::v1::bind::Bind::Gesture(gesturebind) => BindInfoKind::Gesture {
direction: GestureDirection::try_from(gesturebind.direction)
.expect("invalid gesture direction value"),
fingers: gesturebind.fingers,
},
};

let layer = BindLayer {
Expand Down
54 changes: 54 additions & 0 deletions api/rust/src/snowcap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use std::sync::{Arc, OnceLock};

use indexmap::IndexMap;
use pinnacle_api_defs::pinnacle::input::v1::GestureDirection;
use snowcap_api::{
decoration::{DecorationHandle, NewDecorationError},
layer::{ExclusiveZone, KeyboardInteractivity, ZLayer},
Expand Down Expand Up @@ -202,12 +203,42 @@ impl Program for BindOverlay {
}
}

#[derive(PartialEq, Eq, Hash)]
struct GesturebindRepr {
mods: Mod,
direction: String,
fingers: String,
layer: Option<String>,
}

impl std::fmt::Display for GesturebindRepr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mods = format_mods(self.mods);

let layer = self
.layer
.as_ref()
.map(|layer| format!("[{layer}] "))
.unwrap_or_default();

let bind = mods
.as_deref()
.into_iter()
.chain([self.direction.as_str(), self.fingers.as_str()])
.collect::<Vec<_>>()
.join(" + ");
write!(f, "{layer}{bind}")
}
}

#[derive(Default)]
struct GroupBinds {
/// keybinds to descriptions
keybinds: IndexMap<KeybindRepr, Vec<String>>,
/// mousebinds to descriptions
mousebinds: IndexMap<MousebindRepr, Vec<String>>,
/// gesturebinds to descriptions
gesturebinds: IndexMap<GesturebindRepr, Vec<String>>,
}

let bind_infos = crate::input::bind_infos();
Expand Down Expand Up @@ -258,6 +289,29 @@ impl Program for BindOverlay {
descs.push(desc);
}
}
BindInfoKind::Gesture { direction, fingers } => {
let repr = GesturebindRepr {
mods,
direction: match direction {
GestureDirection::Down => "Down",
GestureDirection::Left => "Left",
GestureDirection::Right => "Right",
GestureDirection::Up => "Up",
GestureDirection::DownAndLeft => "Down and Left",
GestureDirection::DownAndRight => "Down and Right",
GestureDirection::UpAndLeft => "Up and Left",
GestureDirection::UpAndRight => "Up and Right",
}
.to_string(),
fingers: format!("{fingers:?} fingers"),
layer,
};

let descs = group.gesturebinds.entry(repr).or_default();
if !desc.is_empty() {
descs.push(desc);
}
}
}
}

Expand Down
Loading
Loading