Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,17 @@ use direct startup. For source builds or archive installations, copy
manager before enabling it. Keep the four executables together. AppImage users
should use automatic daemon startup.

## Keyboard shortcuts

Shortcuts use Command on macOS and Ctrl on Linux and Windows. For example,
**Jump to…** is Cmd+K on macOS and Ctrl+K elsewhere. Menus, tooltips, and
Settings → Keyboard Shortcuts display the keys for your platform; Super is
left to the Linux desktop. Full screen uses Ctrl+Alt+F on Linux and Windows.

Text fields use native editing keys: Ctrl+C/V/X/A/Z on Linux and Windows,
Ctrl+Left/Right to move by word, and Home/End to move within a line. The
optional send shortcut is Ctrl+Enter there and Command+Return on macOS.

## Build from source

Prerequisites:
Expand Down
32 changes: 16 additions & 16 deletions crates/tinyclient/src/gui/app/menubar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,22 +122,22 @@ pub fn menus(places: &[(String, usize)], open_network: Option<Network>, update_i
pub fn bind_keys(cx: &mut App) {
use gpui::KeyBinding;
cx.bind_keys([
KeyBinding::new("cmd-,", Settings, None),
KeyBinding::new("cmd-h", Hide, None),
KeyBinding::new("alt-cmd-h", HideOthers, None),
KeyBinding::new("cmd-n", NewMessage, None),
KeyBinding::new("cmd-u", AttachFile, None),
KeyBinding::new("cmd-w", CloseWindow, None),
KeyBinding::new("cmd-=", BiggerText, None),
KeyBinding::new("cmd-+", BiggerText, None),
KeyBinding::new("cmd--", SmallerText, None),
KeyBinding::new("cmd-0", ActualSize, None),
KeyBinding::new("ctrl-cmd-f", FullScreen, None),
KeyBinding::new("alt-cmd-i", ShowDetails, None),
KeyBinding::new("cmd-shift-c", StartCall, None),
KeyBinding::new("alt-cmd-o", OpenInNetwork, None),
KeyBinding::new("cmd-m", Minimize, None),
KeyBinding::new("cmd-/", KeyboardShortcuts, None),
KeyBinding::new("secondary-,", Settings, None),
KeyBinding::new("secondary-h", Hide, None),
KeyBinding::new("alt-secondary-h", HideOthers, None),
KeyBinding::new("secondary-n", NewMessage, None),
KeyBinding::new("secondary-u", AttachFile, None),
KeyBinding::new("secondary-w", CloseWindow, None),
KeyBinding::new("secondary-=", BiggerText, None),
KeyBinding::new("secondary-+", BiggerText, None),
KeyBinding::new("secondary--", SmallerText, None),
KeyBinding::new("secondary-0", ActualSize, None),
KeyBinding::new(if cfg!(target_os = "macos") { "ctrl-cmd-f" } else { "ctrl-alt-f" }, FullScreen, None),
KeyBinding::new("alt-secondary-i", ShowDetails, None),
KeyBinding::new("secondary-shift-c", StartCall, None),
KeyBinding::new("alt-secondary-o", OpenInNetwork, None),
KeyBinding::new("secondary-m", Minimize, None),
KeyBinding::new("secondary-/", KeyboardShortcuts, None),
]);
}

Expand Down
12 changes: 6 additions & 6 deletions crates/tinyclient/src/gui/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ gpui::actions!(call, [ToggleMute, ToggleDeafen, Leave, Accept, Decline, Jump]);
/// nothing outside this file has to call this.
pub(super) fn bind_keys(cx: &mut App) {
cx.bind_keys([
KeyBinding::new("cmd-shift-m", ToggleMute, None),
KeyBinding::new("cmd-shift-d", ToggleDeafen, None),
KeyBinding::new("cmd-shift-h", Leave, None),
KeyBinding::new("cmd-shift-a", Accept, None),
KeyBinding::new("cmd-shift-r", Decline, None),
KeyBinding::new("cmd-shift-j", Jump, None),
KeyBinding::new("secondary-shift-m", ToggleMute, None),
KeyBinding::new("secondary-shift-d", ToggleDeafen, None),
KeyBinding::new("secondary-shift-h", Leave, None),
KeyBinding::new("secondary-shift-a", Accept, None),
KeyBinding::new("secondary-shift-r", Decline, None),
KeyBinding::new("secondary-shift-j", Jump, None),
]);
}

Expand Down
8 changes: 5 additions & 3 deletions crates/tinyclient/src/gui/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ pub fn field_error_line(words: impl Into<SharedString>) -> Div {

/// A keycap such as `⌘K`.
pub fn keycap(keys: impl Into<SharedString>) -> Div {
let keys: SharedString = keys.into();
div()
.flex_none()
.h(px(18.))
Expand All @@ -428,7 +429,7 @@ pub fn keycap(keys: impl Into<SharedString>) -> Div {
.line_height(px(14.))
.font_weight(theme::MEDIUM)
.text_color(theme::text_faint())
.child(keys.into())
.child(crate::gui::shortcuts::label(&keys))
}

/// The search field's box, with its glyph. The caller adds the input, then a keycap or a clear.
Expand All @@ -451,6 +452,7 @@ pub fn segmented() -> Div {
}

pub fn segment(id: impl Into<ElementId>, label: impl Into<SharedString>, selected: bool) -> Stateful<Div> {
let label: SharedString = label.into();
div()
.id(id)
.h(px(24.))
Expand All @@ -468,7 +470,7 @@ pub fn segment(id: impl Into<ElementId>, label: impl Into<SharedString>, selecte
segment.text_color(theme::text_soft()).hover(|style| style.text_color(theme::text()))
}
})
.child(label.into())
.child(crate::gui::shortcuts::label(&label))
}

/// A 1 pt line between sections.
Expand Down Expand Up @@ -569,7 +571,7 @@ fn empty_state_body(icon: Svg, title: &str, detail: &str) -> Div {
.text_center()
.type_ui()
.text_color(theme::text_soft())
.child(detail.to_owned()),
.child(crate::gui::shortcuts::label(detail)),
)
})
}
Expand Down
5 changes: 3 additions & 2 deletions crates/tinyclient/src/gui/components/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ pub fn tip(
shortcut: Option<&'static str>,
) -> impl Fn(&mut Window, &mut App) -> AnyView + 'static {
let words: SharedString = words.into();
let words: SharedString = crate::gui::shortcuts::label(&words).into();
move |_, cx| {
let words = words.clone();
cx.new(|_| Tip { words, shortcut: shortcut.map(SharedString::from) }).into()
cx.new(|_| Tip { words, shortcut: shortcut.map(|keys| crate::gui::shortcuts::label(keys).into()) }).into()
}
}

Expand Down Expand Up @@ -115,7 +116,7 @@ fn menu_row(
.type_small()
.text_color(theme::text_faint())
.group_hover("menu-item", |style| style.text_color(theme::text_soft()))
.child(keys)
.child(crate::gui::shortcuts::label(keys))
}))
}

Expand Down
2 changes: 1 addition & 1 deletion crates/tinyclient/src/gui/conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,7 @@ impl Pane {
}
"home" => self.list.scroll_to(ListOffset::default()),
"end" => self.go_to_bottom(cx),
"c" if keys.modifiers.platform && self.selected_text().is_some() => self.copy_selection(cx),
"c" if crate::gui::shortcuts::primary(keys.modifiers) && self.selected_text().is_some() => self.copy_selection(cx),
_ => return,
}
cx.stop_propagation();
Expand Down
4 changes: 2 additions & 2 deletions crates/tinyclient/src/gui/conversation/viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ impl Pane {
/// True when the key was the viewer's.
pub(super) fn viewer_key(&mut self, event: &KeyDownEvent, window: &mut Window, cx: &mut Context<Self>) -> bool {
let keys = &event.keystroke;
let command = keys.modifiers.platform;
let command = crate::gui::shortcuts::primary(keys.modifiers);
let fit = self.fit_now(window);
match (keys.key.as_str(), command) {
("escape", _) => self.close_viewer(cx),
Expand Down Expand Up @@ -583,7 +583,7 @@ impl Pane {
.child(picture)
.on_scroll_wheel(cx.listener(move |this, event: &gpui::ScrollWheelEvent, _, cx| {
let delta = event.delta.pixel_delta(px(20.));
if event.modifiers.platform {
if crate::gui::shortcuts::primary(event.modifiers) {
// A notch at a time, however far the wheel says it turned.
if delta.y != px(0.) {
this.zoom_step(delta.y > px(0.), fit, cx);
Expand Down
92 changes: 50 additions & 42 deletions crates/tinyclient/src/gui/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,71 +74,79 @@ actions!(picker, [Star]);
actions!(composer, [PickEmoji, PickGifs]);

pub fn bind_keys(cx: &mut App) {
let context = Some("TextInput");
cx.bind_keys(text_bindings());
// F stars the chosen GIF or sticker; with no cell chosen it is a letter for the search.
cx.bind_keys([KeyBinding::new("f", Star, Some("PickerGrid"))]);
let composer = Some("Composer");
cx.bind_keys([
KeyBinding::new("secondary-e", PickEmoji, composer),
KeyBinding::new("secondary-shift-g", PickGifs, composer),
]);
}

pub(super) fn text_bindings() -> Vec<KeyBinding> {
let context = Some("TextInput");
let bindings = vec![
KeyBinding::new("backspace", Backspace, context),
KeyBinding::new("shift-backspace", Backspace, context),
KeyBinding::new("ctrl-h", Backspace, context),
KeyBinding::new("delete", Delete, context),
KeyBinding::new("ctrl-d", Delete, context),
KeyBinding::new("alt-backspace", DeleteWordLeft, context),
KeyBinding::new("ctrl-w", DeleteWordLeft, context),
KeyBinding::new("alt-delete", DeleteWordRight, context),
KeyBinding::new("cmd-backspace", DeleteToLineStart, context),
KeyBinding::new("ctrl-k", DeleteToLineEnd, context),
KeyBinding::new(if cfg!(target_os = "macos") { "alt-backspace" } else { "ctrl-backspace" }, DeleteWordLeft, context),
KeyBinding::new(if cfg!(target_os = "macos") { "alt-delete" } else { "ctrl-delete" }, DeleteWordRight, context),
KeyBinding::new(if cfg!(target_os = "macos") { "cmd-backspace" } else { "ctrl-shift-backspace" }, DeleteToLineStart, context),
KeyBinding::new("left", Left, context),
KeyBinding::new("ctrl-b", Left, context),
KeyBinding::new("right", Right, context),
KeyBinding::new("ctrl-f", Right, context),
KeyBinding::new("up", Up, context),
KeyBinding::new("ctrl-p", Up, context),
KeyBinding::new("down", Down, context),
KeyBinding::new("ctrl-n", Down, context),
KeyBinding::new("alt-left", WordLeft, context),
KeyBinding::new("alt-right", WordRight, context),
KeyBinding::new("cmd-left", LineStart, context),
KeyBinding::new(if cfg!(target_os = "macos") { "alt-left" } else { "ctrl-left" }, WordLeft, context),
KeyBinding::new(if cfg!(target_os = "macos") { "alt-right" } else { "ctrl-right" }, WordRight, context),
KeyBinding::new("home", LineStart, context),
KeyBinding::new("ctrl-a", LineStart, context),
KeyBinding::new("cmd-right", LineEnd, context),
KeyBinding::new("end", LineEnd, context),
KeyBinding::new("ctrl-e", LineEnd, context),
KeyBinding::new("cmd-up", DocStart, context),
KeyBinding::new("cmd-down", DocEnd, context),
KeyBinding::new(if cfg!(target_os = "macos") { "cmd-up" } else { "ctrl-home" }, DocStart, context),
KeyBinding::new(if cfg!(target_os = "macos") { "cmd-down" } else { "ctrl-end" }, DocEnd, context),
KeyBinding::new("shift-left", SelectLeft, context),
KeyBinding::new("shift-right", SelectRight, context),
KeyBinding::new("shift-up", SelectUp, context),
KeyBinding::new("shift-down", SelectDown, context),
KeyBinding::new("alt-shift-left", SelectWordLeft, context),
KeyBinding::new("alt-shift-right", SelectWordRight, context),
KeyBinding::new("cmd-shift-left", SelectLineStart, context),
KeyBinding::new(if cfg!(target_os = "macos") { "alt-shift-left" } else { "ctrl-shift-left" }, SelectWordLeft, context),
KeyBinding::new(if cfg!(target_os = "macos") { "alt-shift-right" } else { "ctrl-shift-right" }, SelectWordRight, context),
KeyBinding::new("shift-home", SelectLineStart, context),
KeyBinding::new("cmd-shift-right", SelectLineEnd, context),
KeyBinding::new("shift-end", SelectLineEnd, context),
KeyBinding::new("cmd-shift-up", SelectDocStart, context),
KeyBinding::new("cmd-shift-down", SelectDocEnd, context),
KeyBinding::new("cmd-a", SelectAll, context),
KeyBinding::new("cmd-v", Paste, context),
KeyBinding::new("cmd-c", Copy, context),
KeyBinding::new("cmd-x", Cut, context),
KeyBinding::new("cmd-z", Undo, context),
KeyBinding::new("cmd-shift-z", Redo, context),
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, context),
KeyBinding::new(if cfg!(target_os = "macos") { "cmd-shift-up" } else { "ctrl-shift-home" }, SelectDocStart, context),
KeyBinding::new(if cfg!(target_os = "macos") { "cmd-shift-down" } else { "ctrl-shift-end" }, SelectDocEnd, context),
KeyBinding::new("secondary-a", SelectAll, context),
KeyBinding::new("secondary-v", Paste, context),
KeyBinding::new("secondary-c", Copy, context),
KeyBinding::new("secondary-x", Cut, context),
KeyBinding::new("secondary-z", Undo, context),
KeyBinding::new("secondary-shift-z", Redo, context),
KeyBinding::new(if cfg!(target_os = "macos") { "ctrl-cmd-space" } else { "ctrl-alt-space" }, ShowCharacterPalette, context),
KeyBinding::new("enter", Submit, context),
KeyBinding::new("cmd-enter", SubmitAlways, context),
KeyBinding::new("secondary-enter", SubmitAlways, context),
KeyBinding::new("shift-enter", Newline, context),
KeyBinding::new("alt-enter", SubmitAlt, context),
KeyBinding::new("escape", Escape, context),
KeyBinding::new("tab", Tab, context),
KeyBinding::new("pageup", PageUp, context),
KeyBinding::new("pagedown", PageDown, context),
]);
// F stars the chosen GIF or sticker; with no cell chosen it is a letter for the search.
cx.bind_keys([KeyBinding::new("f", Star, Some("PickerGrid"))]);
let composer = Some("Composer");
cx.bind_keys([
KeyBinding::new("cmd-e", PickEmoji, composer),
KeyBinding::new("cmd-shift-g", PickGifs, composer),
]);
];
#[cfg(target_os = "macos")]
let bindings = bindings.into_iter().chain([
KeyBinding::new("cmd-left", LineStart, context),
KeyBinding::new("cmd-right", LineEnd, context),
KeyBinding::new("cmd-shift-left", SelectLineStart, context),
KeyBinding::new("cmd-shift-right", SelectLineEnd, context),
KeyBinding::new("ctrl-h", Backspace, context),
KeyBinding::new("ctrl-d", Delete, context),
KeyBinding::new("ctrl-w", DeleteWordLeft, context),
KeyBinding::new("ctrl-k", DeleteToLineEnd, context),
KeyBinding::new("ctrl-b", Left, context),
KeyBinding::new("ctrl-f", Right, context),
KeyBinding::new("ctrl-p", Up, context),
KeyBinding::new("ctrl-n", Down, context),
KeyBinding::new("ctrl-a", LineStart, context),
KeyBinding::new("ctrl-e", LineEnd, context),
]).collect();
bindings
}

pub enum InputEvent {
Expand Down
41 changes: 23 additions & 18 deletions crates/tinyclient/src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod rail;
mod settings;
mod sidebar;
mod signin;
mod shortcuts;
mod state;
mod theme;
mod time;
Expand Down Expand Up @@ -160,15 +161,19 @@ fn typed_text(text: &str) -> bool {

fn bind_keys(cx: &mut App) {
app::menubar::bind_keys(cx);
cx.bind_keys(shell_bindings());
}

fn shell_bindings() -> Vec<KeyBinding> {
let shell = Some("Shell");
cx.bind_keys([
KeyBinding::new("cmd-q", Quit, None),
KeyBinding::new("cmd-k", Switcher, None),
KeyBinding::new("cmd-[", Back, None),
KeyBinding::new("cmd-]", Forward, None),
KeyBinding::new("cmd-up", UpLevel, shell),
KeyBinding::new("cmd-\\", ToggleSidebar, None),
KeyBinding::new("cmd-.", CloseRight, None),
vec![
KeyBinding::new("secondary-q", Quit, None),
KeyBinding::new("secondary-k", Switcher, None),
KeyBinding::new("secondary-[", Back, None),
KeyBinding::new("secondary-]", Forward, None),
KeyBinding::new("secondary-up", UpLevel, shell),
KeyBinding::new("secondary-\\", ToggleSidebar, None),
KeyBinding::new("secondary-.", CloseRight, None),
KeyBinding::new("escape", CloseTop, shell),
KeyBinding::new("shift-escape", MarkRead, shell),
KeyBinding::new("tab", FocusNext, shell),
Expand All @@ -182,16 +187,16 @@ fn bind_keys(cx: &mut App) {
KeyBinding::new("enter", ListOpen, shell),
KeyBinding::new("left", ListFold, shell),
KeyBinding::new("right", ListUnfold, shell),
KeyBinding::new("cmd-1", Place1, None),
KeyBinding::new("cmd-2", Place2, None),
KeyBinding::new("cmd-3", Place3, None),
KeyBinding::new("cmd-4", Place4, None),
KeyBinding::new("cmd-5", Place5, None),
KeyBinding::new("cmd-6", Place6, None),
KeyBinding::new("cmd-7", Place7, None),
KeyBinding::new("cmd-8", Place8, None),
KeyBinding::new("cmd-9", Place9, None),
]);
KeyBinding::new("secondary-1", Place1, None),
KeyBinding::new("secondary-2", Place2, None),
KeyBinding::new("secondary-3", Place3, None),
KeyBinding::new("secondary-4", Place4, None),
KeyBinding::new("secondary-5", Place5, None),
KeyBinding::new("secondary-6", Place6, None),
KeyBinding::new("secondary-7", Place7, None),
KeyBinding::new("secondary-8", Place8, None),
KeyBinding::new("secondary-9", Place9, None),
]
}

/// `--shot out.png` draws one state with live data, writes the frame and quits, so every state
Expand Down
3 changes: 2 additions & 1 deletion crates/tinyclient/src/gui/settings/controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ pub fn group_label(words: impl Into<SharedString>) -> Div {

/// The words under a card, 6 below it.
pub fn caption(words: impl Into<SharedString>) -> Div {
div().pt(px(6.)).px(px(12.)).max_w(px(456.)).type_small().text_color(theme::text_faint()).child(words.into())
let words: SharedString = words.into();
div().pt(px(6.)).px(px(12.)).max_w(px(456.)).type_small().text_color(theme::text_faint()).child(crate::gui::shortcuts::label(&words))
}

/// A row: its label (and the line saying what the value does) on the left, the control on the
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyclient/src/gui/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ pub fn note_changed(cx: &mut App) {
pub fn attach(shell: WeakEntity<Shell>, cx: &mut App) {
cx.default_global::<Host>().shell = Some(shell);
cx.bind_keys([
KeyBinding::new("cmd-f", Find, Some("Settings")),
KeyBinding::new("secondary-f", Find, Some("Settings")),
KeyBinding::new("escape", Dismiss, Some("Settings")),
KeyBinding::new("tab", NextControl, Some("Settings")),
KeyBinding::new("shift-tab", PreviousControl, Some("Settings")),
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyclient/src/gui/settings/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ impl SettingsWindow {
control = control.child(
segment(SharedString::from(format!("{id}-{ix}")), *choice, ix == current)
.role(gpui::Role::RadioButton)
.aria_label(*choice)
.aria_label(crate::gui::shortcuts::label(choice))
.aria_selected(ix == current)
.on_click(cx.listener(move |this, _, _, cx| {
if ix != current {
Expand Down
Loading
Loading