help text
This commit is contained in:
+2
-2
@@ -130,8 +130,8 @@ pub struct App {
|
||||
}
|
||||
|
||||
/// Counters worth reporting with --verbose. Live capture is easy to get subtly
|
||||
/// wrong — a starved buffer pool or a clock that never ticks both look like
|
||||
/// "nothing updates" — so the numbers that distinguish those stay available.
|
||||
/// wrong - a starved buffer pool or a clock that never ticks both look like
|
||||
/// "nothing updates" - so the numbers that distinguish those stay available.
|
||||
#[derive(Default)]
|
||||
pub struct Stats {
|
||||
/// Frame callbacks received, i.e. how often the live clock fired.
|
||||
|
||||
+4
-4
@@ -34,8 +34,8 @@ use crate::app::App;
|
||||
use crate::shm;
|
||||
use crate::target::Kind;
|
||||
|
||||
/// One capture buffer. `busy` means the compositor still holds it — either it is
|
||||
/// on screen or a capture is writing into it — so we must not scribble over it.
|
||||
/// One capture buffer. `busy` means the compositor still holds it - either it is
|
||||
/// on screen or a capture is writing into it - so we must not scribble over it.
|
||||
pub struct Slot {
|
||||
pub(crate) buffer: WlBuffer,
|
||||
pub(crate) busy: bool,
|
||||
@@ -299,7 +299,7 @@ impl App {
|
||||
continue;
|
||||
}
|
||||
// Nor is there any point refreshing a tile that is scrolled out of
|
||||
// sight — that is a readback for pixels nobody sees.
|
||||
// sight - that is a readback for pixels nobody sees.
|
||||
if self.layout.tile(i as i32, self.scroll).is_none() {
|
||||
continue;
|
||||
}
|
||||
@@ -394,7 +394,7 @@ impl Dispatch<ExtImageCopyCaptureFrameV1, usize> for App {
|
||||
/// Release is the whole contract: with wl_shm the compositor copies the pixels
|
||||
/// out at commit and hands the buffer straight back, so the slot currently on
|
||||
/// screen is usually free too. (Waiting for it to stop being the displayed slot
|
||||
/// instead would deadlock — that release never comes twice.)
|
||||
/// instead would deadlock - that release never comes twice.)
|
||||
impl Dispatch<WlBuffer, (usize, usize)> for App {
|
||||
fn event(
|
||||
app: &mut Self,
|
||||
|
||||
+16
-30
@@ -10,19 +10,16 @@ use crate::target::Format;
|
||||
use crate::theme::Theme;
|
||||
|
||||
const HELP: &str = "\
|
||||
wl-pick — a live grid of window and display previews, for picking one
|
||||
wl-rab - a simple alt-tab switcher for sway
|
||||
|
||||
usage: wl-pick [options]
|
||||
usage: wl-tab [options]
|
||||
|
||||
--config PATH config file [~/.config/wl-pick/config]
|
||||
--format tsv|json|portal how to report the pick [tsv]
|
||||
--live all|current|none which tiles keep updating live [all]
|
||||
(displays are always a single snapshot)
|
||||
--fps N cap on live updates per tile per second [12]
|
||||
--outputs, --no-outputs include whole displays as tiles [no]
|
||||
--labels, --no-labels a label under each thumbnail [yes]
|
||||
--order mru|tree window ordering: mru or layout tree [mru]
|
||||
--focus, --no-focus focus the picked target in sway directly [no]
|
||||
--focus, --no-focus focus the picked target directly [no]
|
||||
--font FAMILY label font family [the system monospace font]
|
||||
--font-size PX label size in logical px [13.3]
|
||||
--timeout SECS exit anyway after SECS, in case the keyboard
|
||||
@@ -30,12 +27,6 @@ usage: wl-pick [options]
|
||||
-v, --verbose phase timings, tile list and capture stats
|
||||
-h, --help this
|
||||
|
||||
keys: arrows, hjkl or Tab/Shift+Tab move; PgUp/PgDn and Home/End jump;
|
||||
Enter picks; Escape or q cancels; in Alt+Tab mode, releasing
|
||||
the modifier (Alt/Super) picks the selection.
|
||||
mouse: click a tile to pick it, scroll to move. Hovering does not move the
|
||||
selection, and a click outside a tile does nothing.
|
||||
|
||||
The pick goes to stdout and nothing does if you cancel, so exit status is
|
||||
0 for a pick and 1 for a cancel. Acting on it is the caller's job.
|
||||
|
||||
@@ -55,9 +46,6 @@ config:
|
||||
|
||||
max-width = 90ppt # the box the grid may fill
|
||||
max-height = 90ppt
|
||||
max-columns = 4 # thumbnails are the box divided by these,
|
||||
max-rows = 4 # so their size never depends on how many
|
||||
# windows are open; further rows scroll
|
||||
|
||||
font = monospace # also --font
|
||||
font-size = 13.3
|
||||
@@ -90,18 +78,7 @@ formats:
|
||||
|
||||
[screencast]
|
||||
chooser_type=simple
|
||||
chooser_cmd=wl-pick --format portal
|
||||
|
||||
focusing on sway:
|
||||
|
||||
wl-pick --focus or:
|
||||
|
||||
IFS=$'\\t' read -r type id toplevel app title < <(wl-pick) &&
|
||||
case $type in
|
||||
window) swaymsg \"[con_id=$id] focus\" ;;
|
||||
output) swaymsg \"focus output $id\" ;;
|
||||
esac
|
||||
";
|
||||
chooser_cmd=wl-pick --format portal";
|
||||
|
||||
/// What the command line asked for. Every setting is optional so the config file
|
||||
/// can fill the gaps: a flag beats the file, the file beats the default.
|
||||
@@ -316,10 +293,19 @@ mod tests {
|
||||
// With no flag the file decides, and with no file either, the default (outputs: false).
|
||||
assert!(!args(&[]).resolve(&file(false), &display()).outputs);
|
||||
assert!(!args(&[]).resolve(&Config::default(), &display()).outputs);
|
||||
assert!(args(&["--outputs"]).resolve(&Config::default(), &display()).outputs);
|
||||
assert_eq!(args(&[]).resolve(&Config::default(), &display()).order, Order::Mru);
|
||||
assert!(
|
||||
args(&["--outputs"])
|
||||
.resolve(&Config::default(), &display())
|
||||
.outputs
|
||||
);
|
||||
assert_eq!(
|
||||
args(&["--order", "tree"]).resolve(&Config::default(), &display()).order,
|
||||
args(&[]).resolve(&Config::default(), &display()).order,
|
||||
Order::Mru
|
||||
);
|
||||
assert_eq!(
|
||||
args(&["--order", "tree"])
|
||||
.resolve(&Config::default(), &display())
|
||||
.order,
|
||||
Order::Tree
|
||||
);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,13 +1,13 @@
|
||||
//! The config file: `~/.config/wl-pick/config`.
|
||||
//!
|
||||
//! Flat `key = value` lines with `#` comments — no sections, no nesting, so a
|
||||
//! Flat `key = value` lines with `#` comments - no sections, no nesting, so a
|
||||
//! TOML parser would be a dependency bought for nothing. Every setting is
|
||||
//! optional; anything absent keeps its default, and a command-line flag beats
|
||||
//! the file.
|
||||
//!
|
||||
//! Sizes take sway's syntax: `600px` is absolute, `70ppt` is 70 percent of the
|
||||
//! display the grid appears on. That matters on a multi-monitor setup, where a
|
||||
//! pixel size that suits one screen is wrong on the next — percentages are
|
||||
//! pixel size that suits one screen is wrong on the next - percentages are
|
||||
//! resolved against whichever display the overlay actually maps on, each time
|
||||
//! it runs.
|
||||
|
||||
|
||||
+8
-8
@@ -11,13 +11,13 @@
|
||||
//! is no thumbnail encoding, no scaler, and no full-resolution image in our
|
||||
//! address space.
|
||||
//!
|
||||
//! - `cli` — flags and help
|
||||
//! - `sway` — the window list, over sway's IPC socket
|
||||
//! - `target` — what a tile stands for, and how a pick is reported
|
||||
//! - `app` — the Wayland client state everything dispatches into
|
||||
//! - `capture` — capture sessions and their buffers
|
||||
//! - `overlay` — the layer surface, the drawing, the keyboard
|
||||
//! - `theme`, `text`, `shm` — look, labels, and shared memory
|
||||
//! - `cli` - flags and help
|
||||
//! - `sway` - the window list, over sway's IPC socket
|
||||
//! - `target` - what a tile stands for, and how a pick is reported
|
||||
//! - `app` - the Wayland client state everything dispatches into
|
||||
//! - `capture` - capture sessions and their buffers
|
||||
//! - `overlay` - the layer surface, the drawing, the keyboard
|
||||
//! - `theme`, `text`, `shm` - look, labels, and shared memory
|
||||
|
||||
// `slice::as_chunks` and friends, which clippy suggests in place of
|
||||
// `chunks_exact`, are newer than the toolchain this crate says it supports.
|
||||
@@ -272,7 +272,7 @@ fn run() -> Result<ExitCode, Box<dyn Error>> {
|
||||
/// Every wait before the overlay is interactive is bounded, because a
|
||||
/// compositor is entitled to simply never answer. sway does exactly that for a
|
||||
/// capture request on a toplevel another client is already capturing: no frame,
|
||||
/// no `failed`, no `stopped`, just silence — and an unbounded wait on that is a
|
||||
/// no `failed`, no `stopped`, just silence - and an unbounded wait on that is a
|
||||
/// picker with no window that has to be killed from another terminal.
|
||||
fn pump_for(
|
||||
conn: &Connection,
|
||||
|
||||
+10
-10
@@ -3,7 +3,7 @@
|
||||
//!
|
||||
//! Scaling is the compositor's job. A tile attaches its capture buffer directly
|
||||
//! and wp_viewporter names the rectangle to fit it into, so nothing here touches
|
||||
//! a pixel of window content — only the background, selection and labels.
|
||||
//! a pixel of window content - only the background, selection and labels.
|
||||
|
||||
use std::error::Error;
|
||||
use std::os::fd::AsFd;
|
||||
@@ -139,7 +139,7 @@ impl App {
|
||||
/// Put every visible tile where the viewport says, and unmap the rest.
|
||||
///
|
||||
/// Runs again after each scroll, so a tile scrolled off screen gets a null
|
||||
/// buffer — the way to hide a subsurface — rather than being left behind.
|
||||
/// buffer - the way to hide a subsurface - rather than being left behind.
|
||||
/// Scaling stays the compositor's job: the capture buffer is attached as it
|
||||
/// is, and wp_viewporter names the rectangle to fit it into.
|
||||
pub fn sync_tiles(&mut self, qh: &QueueHandle<Self>) {
|
||||
@@ -164,8 +164,8 @@ impl App {
|
||||
let surface = self.compositor.create_surface(qh, ());
|
||||
let subsurface = self.subcompositor.get_subsurface(&surface, &parent, qh, ());
|
||||
let viewport = self.viewporter.get_viewport(&surface, qh, ());
|
||||
// Tiles change independently of the chrome — a live frame
|
||||
// arrives whenever its window does — so they must not wait on a
|
||||
// Tiles change independently of the chrome - a live frame
|
||||
// arrives whenever its window does - so they must not wait on a
|
||||
// parent commit.
|
||||
subsurface.set_desync();
|
||||
// The capture protocol reports the transform the compositor
|
||||
@@ -239,7 +239,7 @@ impl App {
|
||||
let (cw, ch) = (chrome.w, chrome.h);
|
||||
let mut p = chrome.painter();
|
||||
p.fill(bg);
|
||||
// The selection fills the whole element box, padding included — the same
|
||||
// The selection fills the whole element box, padding included - the same
|
||||
// thing rofi's element background does. It can be scrolled out of sight.
|
||||
if let Some(elem) = elem {
|
||||
p.rect(elem, sel_bg);
|
||||
@@ -293,7 +293,7 @@ impl App {
|
||||
}
|
||||
|
||||
/// The tile under the pointer, if it is over one. A tile's own subsurface
|
||||
/// answers directly; over the parent surface — padding, labels, gaps — the
|
||||
/// answers directly; over the parent surface - padding, labels, gaps - the
|
||||
/// layout is asked instead.
|
||||
fn tile_at_pointer(&self) -> Option<usize> {
|
||||
let hover = self.hover.as_ref()?;
|
||||
@@ -308,8 +308,8 @@ impl App {
|
||||
})
|
||||
}
|
||||
|
||||
/// Press and release on the same tile picks it. Anywhere else — the margin,
|
||||
/// a gap, an empty cell of the last row — does nothing at all.
|
||||
/// Press and release on the same tile picks it. Anywhere else - the margin,
|
||||
/// a gap, an empty cell of the last row - does nothing at all.
|
||||
fn click(&mut self, pressed: bool) {
|
||||
if pressed {
|
||||
self.pressed = self.tile_at_pointer();
|
||||
@@ -523,7 +523,7 @@ impl Dispatch<WlKeyboard, ()> for App {
|
||||
wl_keyboard::Event::Enter { keys, .. } => app.keyboard_enter(keys, qh),
|
||||
wl_keyboard::Event::Leave { .. } => {
|
||||
app.focused = false;
|
||||
// Clear any held repeat — we no longer have the keyboard.
|
||||
// Clear any held repeat - we no longer have the keyboard.
|
||||
app.repeat_key = None;
|
||||
app.repeat_next = None;
|
||||
}
|
||||
@@ -532,7 +532,7 @@ impl Dispatch<WlKeyboard, ()> for App {
|
||||
}
|
||||
}
|
||||
|
||||
/// Hovering does not move the selection — that belongs to the keyboard — so the
|
||||
/// Hovering does not move the selection - that belongs to the keyboard - so the
|
||||
/// pointer only tracks where it is and what it clicked. Scrolling is a
|
||||
/// deliberate gesture, so that does move the selection.
|
||||
impl Dispatch<WlPointer, ()> for App {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
//!
|
||||
//! Capture buffers deliberately never get mapped into this process. The
|
||||
//! compositor writes the window pixels and then samples them again for display,
|
||||
//! so we only need the fd — mapping them would fault ~7 MB per window into our
|
||||
//! so we only need the fd - mapping them would fault ~7 MB per window into our
|
||||
//! address space for nothing.
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
+2
-6
@@ -186,11 +186,7 @@ pub fn windows(conn: &mut Connection, order: Order) -> Result<Vec<Target>, swayi
|
||||
return (1, idx, 0);
|
||||
}
|
||||
}
|
||||
if t.visible {
|
||||
(2, 0, 0)
|
||||
} else {
|
||||
(3, 0, 0)
|
||||
}
|
||||
if t.visible { (2, 0, 0) } else { (3, 0, 0) }
|
||||
});
|
||||
}
|
||||
Ok(out)
|
||||
@@ -228,7 +224,7 @@ fn collect_tree(node: &Node, out: &mut Vec<Target>) {
|
||||
/// One active display: what the overlay needs to size itself against.
|
||||
///
|
||||
/// The overlay maps on the focused display, so percentages and the buffer scale
|
||||
/// are resolved against *that* one — on a mixed-DPI, mixed-size setup the
|
||||
/// are resolved against *that* one - on a mixed-DPI, mixed-size setup the
|
||||
/// numbers differ per monitor, and taking the largest of everything would be
|
||||
/// wrong on all but one.
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
//! What a tile stands for: a window, or a whole display.
|
||||
//!
|
||||
//! Both are capture sources as far as the protocol is concerned — one from a
|
||||
//! foreign-toplevel handle, one from a `wl_output` — so the grid treats them
|
||||
//! Both are capture sources as far as the protocol is concerned - one from a
|
||||
//! foreign-toplevel handle, one from a `wl_output` - so the grid treats them
|
||||
//! alike and only differs in how it labels them and what picking one does.
|
||||
|
||||
use std::fmt;
|
||||
@@ -110,8 +110,8 @@ impl Target {
|
||||
/// `IFS=$'\t' read -r type id toplevel app title`.
|
||||
///
|
||||
/// Both identifiers are there because both get used: sway scripting acts on
|
||||
/// the con_id (`[con_id=N] focus`), while tools that capture a window —
|
||||
/// grim -T, the desktop portal — want the foreign-toplevel identifier.
|
||||
/// the con_id (`[con_id=N] focus`), while tools that capture a window -
|
||||
/// grim -T, the desktop portal - want the foreign-toplevel identifier.
|
||||
pub fn tsv(&self) -> String {
|
||||
format!(
|
||||
"{}\t{}\t{}\t{}\t{}",
|
||||
@@ -162,7 +162,7 @@ impl Target {
|
||||
|
||||
/// What xdg-desktop-portal-wlr's `simple` chooser accepts: `Monitor: NAME`
|
||||
/// or `Window: <foreign-toplevel identifier>`. A window the compositor never
|
||||
/// gave an identifier for cannot be named this way, hence the Option — and
|
||||
/// gave an identifier for cannot be named this way, hence the Option - and
|
||||
/// an empty stdout is exactly how that chooser says "declined".
|
||||
pub fn portal(&self) -> Option<String> {
|
||||
match self.kind {
|
||||
|
||||
+4
-4
@@ -6,7 +6,7 @@
|
||||
//! and joined after them: by the time anything is drawn, every label is shaped
|
||||
//! and its glyphs are already in the cache, and painting one costs ~0.1ms.
|
||||
//!
|
||||
//! Sizes here are physical pixels — the caller scales logical units first,
|
||||
//! Sizes here are physical pixels - the caller scales logical units first,
|
||||
//! because the chrome buffer it paints into is physical too.
|
||||
|
||||
use std::thread::{self, JoinHandle};
|
||||
@@ -54,7 +54,7 @@ const MONO_CANDIDATES: &[&str] = &[
|
||||
|
||||
/// Load the smallest font database that can render `family`.
|
||||
///
|
||||
/// `FontSystem::new()` scans every system font, which costs ~37ms — most of the
|
||||
/// `FontSystem::new()` scans every system font, which costs ~37ms - most of the
|
||||
/// startup budget. A user's own font directories are tiny by comparison, so try
|
||||
/// those first and only pay for the full scan when the family really isn't there
|
||||
/// (which is also what makes an unknown family fall back gracefully). The
|
||||
@@ -87,7 +87,7 @@ fn has_family(db: &fontdb::Database, family: &str) -> bool {
|
||||
/// Turn the generic default into a real family name.
|
||||
///
|
||||
/// cosmic-text's own generic resolves through fontdb's built-in preference
|
||||
/// ("FreeMono"), which is usually absent and then lands on an arbitrary face — so
|
||||
/// ("FreeMono"), which is usually absent and then lands on an arbitrary face - so
|
||||
/// ask fontconfig instead, since that is what the rest of the desktop uses. A
|
||||
/// named family passes through untouched; if it turns out to be missing,
|
||||
/// cosmic-text falls back on its own.
|
||||
@@ -145,7 +145,7 @@ fn build(texts: Vec<String>, family: String, font_px: f32, line_h: f32, box_w: f
|
||||
}
|
||||
}
|
||||
|
||||
/// Shorten `text` until it fits in `box_w`, ending with an ellipsis — window
|
||||
/// Shorten `text` until it fits in `box_w`, ending with an ellipsis - window
|
||||
/// titles are arbitrarily long, and rofi ellipsised them too.
|
||||
fn ellipsize(
|
||||
fs: &mut FontSystem,
|
||||
|
||||
+4
-4
@@ -7,7 +7,7 @@
|
||||
//! Sizing works from caps rather than from a thumbnail size. The config gives a
|
||||
//! box the grid may fill and a column and row limit; a thumbnail is that box
|
||||
//! divided by those limits. So a thumbnail is the same size whether one window
|
||||
//! is open or thirty — the overlay hugs whatever is there, and rows past the
|
||||
//! is open or thirty - the overlay hugs whatever is there, and rows past the
|
||||
//! limit scroll.
|
||||
|
||||
/// 0xAARRGGBB, premultiplied (everything here is opaque).
|
||||
@@ -24,7 +24,7 @@ pub struct Theme {
|
||||
pub border_px: i32,
|
||||
/// The box the grid may not exceed, in logical px. Thumbnails are sized to
|
||||
/// divide it by the column and row caps below, so a thumbnail is the same
|
||||
/// size whether one window is open or thirty — only the window around them
|
||||
/// size whether one window is open or thirty - only the window around them
|
||||
/// shrinks to hug what is there.
|
||||
pub max_w: i32,
|
||||
pub max_h: i32,
|
||||
@@ -108,8 +108,8 @@ impl Layout {
|
||||
/// it does not change with how many windows are open: one window gets a
|
||||
/// normal thumbnail in a small overlay, thirty get the same thumbnail and
|
||||
/// scroll. Columns follow ceil(sqrt(n)) up to the cap, so a handful of
|
||||
/// windows makes a tidy grid rather than one long row — the rule rofigrid
|
||||
/// used — and the overlay hugs whatever is there.
|
||||
/// windows makes a tidy grid rather than one long row - the rule rofigrid
|
||||
/// used - and the overlay hugs whatever is there.
|
||||
/// Lay out `n` tiles in a single row for a display of the given logical size.
|
||||
///
|
||||
/// The overlay and the window previews resize automatically to fit all `n`
|
||||
|
||||
Reference in New Issue
Block a user