mirror of
https://github.com/sharkdp/bat
synced 2026-06-09 10:03:18 +00:00
Merge branch 'master' into add-cobol-syntax
This commit is contained in:
@@ -1,15 +1,23 @@
|
||||
# unreleased
|
||||
|
||||
- Fixed bug caused by using `--plain` and `--terminal-width=N` flags simultaneously, see #3529 (@H4k1l)
|
||||
- Fixed syntax tests path, see #3610 (@foxfromworld)
|
||||
|
||||
## Features
|
||||
|
||||
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
|
||||
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
|
||||
- Implement `--unbuffered` mode for streaming input, allowing partial lines to display immediately (e.g. `tail -f | bat -u`). Closes #3555, see #3583 (@mainnebula)
|
||||
- Added an initial `flake.nix` for a ready made development environment; see #3578 (@vorburger)
|
||||
- Add `--quiet-empty` (`-E`) flag to suppress output when input is empty. Closes #1936, see #3563 (@NORMAL-EX)
|
||||
- Improve native man pages and command help syntax highlighting by stripping overstriking, see #3517 (@akirk)
|
||||
- Add `--fallback-syntax`/`--fallback-language` to apply syntax highlighting only when auto-detection fails, see #1341 (@Xavrir)
|
||||
|
||||
## Bugfixes
|
||||
- Fix `BAT_CONFIG_DIR` pointing at system config directory causing duplicate flag errors. Closes #3589, see #3620 (@Xavrir)
|
||||
- Fix syntax highlighting for symlinked files when the symlink name has no extension but the target does. Closes #1001, see #3621 (@Xavrir)
|
||||
- Report error when pager is missing instead of silently falling back, see #3588 (@IMaloney)
|
||||
- Fix `--wrap=never` and `-S` flags being ignored when piping to pager, see #3592 (@IMaloney)
|
||||
- Fix crash with BusyBox `less` on Windows, see #3527 (@Anchal-T)
|
||||
- Fix `bat cache --help` failing with 'unexpected argument' error, see #3580 and #3560 (@NORMAL-EX)
|
||||
- `--help` now correctly honors `--pager=builtin`. See #3516 (@keith-hall)
|
||||
@@ -25,6 +33,7 @@
|
||||
- Change the URL of Zig submodule from GitHub to Codeberg, see #3519 (@sorairolake)
|
||||
- Don't color strings inside CSV files, to make it easier to tell which column they belong to, see #3521 (@keith-hall)
|
||||
- Add syntax highlighting support for COBOL, see #3584 (@adukhan99)
|
||||
- Fixed manpage syntax so that ANSI escape codes don't get incorrectly highlighted and thus broken, see #3586 (@BlueElectivire)
|
||||
|
||||
## Themes
|
||||
|
||||
|
||||
@@ -91,3 +91,29 @@ To learn how to write regression tests for theme and syntax changes, read the
|
||||
[Syntax
|
||||
tests](https://github.com/sharkdp/bat/blob/master/doc/assets.md#syntax-tests)
|
||||
section in `assets.md`.
|
||||
|
||||
### Ensuring bat is available for Syntax tests
|
||||
|
||||
The syntax test script (`tests/syntax-tests/update.sh`) calls `bat` from your PATH and regenerates the highlighted output files under
|
||||
`tests/syntax-tests/highlighted/`. These files are used to verify that syntax highlighting works as expected.
|
||||
|
||||
- If you only built the binaries with:
|
||||
```bash
|
||||
cargo build --bins
|
||||
```
|
||||
|
||||
you need to add the debug build to your PATH from the bat project root before running the tests.
|
||||
See also step 5 in [Syntax
|
||||
tests](https://github.com/sharkdp/bat/blob/master/doc/assets.md#syntax-tests) for related instructions.
|
||||
```bash
|
||||
export PATH="$PATH:$(pwd)/target/debug"
|
||||
```
|
||||
Otherwise, you will see:
|
||||
```bash
|
||||
Error: Could not execute 'bat'. Please make sure that the executable is available on the PATH.
|
||||
```
|
||||
- If you installed bat with:
|
||||
```bash
|
||||
cargo install --path . --locked
|
||||
```
|
||||
then bat will be available in ~/.cargo/bin (usually already in PATH), and the tests will run without issues.
|
||||
Generated
+18
-18
@@ -223,9 +223,9 @@ checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
|
||||
|
||||
[[package]]
|
||||
name = "bytesize"
|
||||
version = "1.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc"
|
||||
checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
@@ -252,18 +252,18 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.56"
|
||||
version = "4.5.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e"
|
||||
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.56"
|
||||
version = "4.5.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0"
|
||||
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -274,9 +274,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||
|
||||
[[package]]
|
||||
name = "clircle"
|
||||
@@ -658,9 +658,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.20.3"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c"
|
||||
checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
@@ -909,9 +909,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
version = "0.2.182"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
@@ -1032,9 +1032,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
@@ -1415,9 +1415,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.16.1"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
|
||||
checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_with_macros",
|
||||
@@ -1425,9 +1425,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.16.1"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
|
||||
checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
|
||||
+5
-5
@@ -69,7 +69,7 @@ etcetera = { version = "0.11.0", optional = true }
|
||||
grep-cli = { version = "0.1.12", optional = true }
|
||||
regex = { version = "1.12.2", optional = true }
|
||||
walkdir = { version = "2.5", optional = true }
|
||||
bytesize = { version = "1.3.0" }
|
||||
bytesize = { version = "2.3.1" }
|
||||
encoding_rs = "0.8.35"
|
||||
execute = { version = "0.2.15", optional = true }
|
||||
terminal-colorsaurus = "1.0"
|
||||
@@ -87,7 +87,7 @@ default-features = false
|
||||
features = ["parsing"]
|
||||
|
||||
[dependencies.clap]
|
||||
version = "4.5.56"
|
||||
version = "4.5.60"
|
||||
optional = true
|
||||
features = ["wrap_help", "cargo"]
|
||||
|
||||
@@ -104,7 +104,7 @@ tempfile = "3.23.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
nix = { version = "0.30", default-features = false, features = ["term"] }
|
||||
nix = { version = "0.31", default-features = false, features = ["term"] }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.97"
|
||||
@@ -117,13 +117,13 @@ quote = "1.0.40"
|
||||
regex = "1.12.2"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_with = { version = "3.16.1", default-features = false, features = ["macros"] }
|
||||
serde_with = { version = "3.17.0", default-features = false, features = ["macros"] }
|
||||
syn = { version = "2.0.104", features = ["full"] }
|
||||
toml = { version = "0.9.8", features = ["preserve_order"] }
|
||||
walkdir = "2.5"
|
||||
|
||||
[build-dependencies.clap]
|
||||
version = "4.5.56"
|
||||
version = "4.5.60"
|
||||
optional = true
|
||||
features = ["wrap_help", "cargo"]
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -9,7 +9,7 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
||||
$ArrayCompletion = @('bash', 'fish', 'zsh', 'ps1')
|
||||
$ArrayWhen = @('auto', 'never', 'always')
|
||||
$ArrayYesNo = @('never', 'always')
|
||||
$ArrayWrap = @('always', 'never', 'character')
|
||||
$ArrayWrap = @('always', 'never', 'character', 'word')
|
||||
$ArrayBinary = @('no-printing', 'as-text')
|
||||
$ArrayPrint = @('unicode', 'caret')
|
||||
|
||||
@@ -135,7 +135,7 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
||||
[CompletionResult]::new('--file-name' , 'file-name' , [CompletionResultType]::ParameterName, 'Specify the name to display for a file.')
|
||||
[CompletionResult]::new('--diff-context' , 'diff-context' , [CompletionResultType]::ParameterName, 'diff-context')
|
||||
[CompletionResult]::new('--tabs' , 'tabs' , [CompletionResultType]::ParameterName, 'Set the tab width to T spaces.')
|
||||
[CompletionResult]::new('--wrap' , 'wrap' , [CompletionResultType]::ParameterName, 'Specify the text-wrapping mode (*auto*, character).')
|
||||
[CompletionResult]::new('--wrap' , 'wrap' , [CompletionResultType]::ParameterName, 'Specify the text-wrapping mode (*auto*, never, character, word).')
|
||||
[CompletionResult]::new('--terminal-width' , 'terminal-width' , [CompletionResultType]::ParameterName, 'Explicitly set the width of the terminal instead of determining it automatically. If prefixed with ''+'' or ''-'', the value will be treated as an offset to the actual terminal width. See also: ''--wrap''.')
|
||||
[CompletionResult]::new('--color' , 'color' , [CompletionResultType]::ParameterName, 'When to use colors (*auto*, never, always).')
|
||||
[CompletionResult]::new('--italic-text' , 'italic-text' , [CompletionResultType]::ParameterName, 'Use italics in output (always, *never*)')
|
||||
|
||||
Vendored
+1
-1
@@ -117,7 +117,7 @@ _bat() {
|
||||
return 0
|
||||
;;
|
||||
--wrap)
|
||||
COMPREPLY=($(compgen -W "auto never character" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "auto never character word" -- "$cur"))
|
||||
return 0
|
||||
;;
|
||||
--binary)
|
||||
|
||||
Vendored
+1
@@ -118,6 +118,7 @@ set -l wrap_opts '
|
||||
auto\tdefault
|
||||
never\t
|
||||
character\t
|
||||
word\t
|
||||
'
|
||||
|
||||
# While --tabs theoretically takes any number, most people should be OK with these.
|
||||
|
||||
Vendored
+1
-1
@@ -34,7 +34,7 @@ _{{PROJECT_EXECUTABLE}}_main() {
|
||||
'(-d --diff)'--diff'[only show lines that have been added/removed/modified]'
|
||||
--diff-context='[specify lines of context around added/removed/modified lines when using `--diff`]:lines'
|
||||
--tabs='[set the tab width]:tab width [4]'
|
||||
--wrap='[specify the text-wrapping mode]:mode [auto]:(auto never character)'
|
||||
--wrap='[specify the text-wrapping mode]:mode [auto]:(auto never character word)'
|
||||
'!(--wrap)'{-S,--chop-long-lines}
|
||||
--terminal-width='[explicitly set the width of the terminal instead of determining it automatically]:width'
|
||||
'(-n --number --diff --diff-context)'{-n,--number}'[show line numbers]'
|
||||
|
||||
Vendored
+4
-2
@@ -102,8 +102,10 @@ Set the tab width to T spaces. Use a width of 0 to pass tabs through directly
|
||||
.HP
|
||||
\fB\-\-wrap\fR <mode>
|
||||
.IP
|
||||
Specify the text\-wrapping mode (*auto*, never, character). The '\-\-terminal\-width' option
|
||||
can be used in addition to control the output width.
|
||||
Specify the text\-wrapping mode (*auto*, never, character, word). The '\-\-terminal\-width' option
|
||||
can be used in addition to control the output width. In \fBword\fR mode, lines are broken at
|
||||
whitespace boundaries. If a single word exceeds the terminal width, it falls back to
|
||||
character wrapping.
|
||||
.HP
|
||||
\fB\-S\fR, \fB\-\-chop\-long\-lines\fR
|
||||
.IP
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ scope: source.man
|
||||
variables:
|
||||
section_heading: '^(?!#)\S.*$'
|
||||
command_line_option: '(--?[A-Za-z0-9][_A-Za-z0-9-]*)'
|
||||
ansi_escape_sequence: '\e\[[\?=]?(?:\d+;?)*[A-Za-z]'
|
||||
|
||||
contexts:
|
||||
prototype:
|
||||
@@ -69,7 +70,7 @@ contexts:
|
||||
escape: '(?={{section_heading}})'
|
||||
|
||||
function-call:
|
||||
- match: '\b([A-Za-z0-9_\-]+\.)?([A-Za-z0-9_\-]+)(\()([^)]*)(\))'
|
||||
- match: '(?<!\e\[)(?:\b|\s)(?:{{ansi_escape_sequence}})?([A-Za-z0-9_\-]+\.)?([A-Za-z0-9_\-]+)(?:{{ansi_escape_sequence}})?(\()([^)]*)(\))'
|
||||
captures:
|
||||
1: entity.name.function.man
|
||||
2: entity.name.function.man
|
||||
|
||||
+9
-2
@@ -37,6 +37,13 @@ Options:
|
||||
name (like 'C++' or 'LaTeX') or possible file extension (like 'cpp', 'hpp' or 'md'). Use
|
||||
'--list-languages' to show all supported language names and file extensions.
|
||||
|
||||
--fallback-syntax <fallback-syntax>
|
||||
Set a fallback language for syntax highlighting when auto-detection fails. Unlike
|
||||
'--language', this is only used when no syntax could be detected from filename, custom
|
||||
syntax mappings, or first-line detection.
|
||||
|
||||
[aliases: --fallback-language]
|
||||
|
||||
-H, --highlight-line <N:M>
|
||||
Highlight the specified line ranges with a different background color For example:
|
||||
'--highlight-line 40' highlights line 40
|
||||
@@ -61,8 +68,8 @@ Options:
|
||||
Set the tab width to T spaces. Use a width of 0 to pass tabs through directly
|
||||
|
||||
--wrap <mode>
|
||||
Specify the text-wrapping mode (*auto*, never, character). The '--terminal-width' option
|
||||
can be used in addition to control the output width.
|
||||
Specify the text-wrapping mode (*auto*, never, character, word). The '--terminal-width'
|
||||
option can be used in addition to control the output width.
|
||||
|
||||
-S, --chop-long-lines
|
||||
Truncate all lines longer than screen width. Alias for '--wrap=never'.
|
||||
|
||||
+3
-1
@@ -17,6 +17,8 @@ Options:
|
||||
Show plain style (alias for '--style=plain').
|
||||
-l, --language <language>
|
||||
Set the language for syntax highlighting.
|
||||
--fallback-syntax <fallback-syntax>
|
||||
Set a fallback language for undetected syntaxes. [aliases: --fallback-language]
|
||||
-H, --highlight-line <N:M>
|
||||
Highlight lines N through M.
|
||||
--file-name <name>
|
||||
@@ -26,7 +28,7 @@ Options:
|
||||
--tabs <T>
|
||||
Set the tab width to T spaces.
|
||||
--wrap <mode>
|
||||
Specify the text-wrapping mode (*auto*, never, character).
|
||||
Specify the text-wrapping mode (*auto*, never, character, word).
|
||||
-S, --chop-long-lines
|
||||
Truncate all lines longer than screen width. Alias for '--wrap=never'.
|
||||
-n, --number
|
||||
|
||||
+143
-15
@@ -210,6 +210,7 @@ impl HighlightingAssets {
|
||||
pub(crate) fn get_syntax(
|
||||
&self,
|
||||
language: Option<&str>,
|
||||
fallback_syntax: Option<&str>,
|
||||
input: &mut OpenedInput,
|
||||
mapping: &SyntaxMapping,
|
||||
) -> Result<SyntaxReferenceInSet<'_>> {
|
||||
@@ -222,21 +223,50 @@ impl HighlightingAssets {
|
||||
}
|
||||
|
||||
let path = input.path();
|
||||
let path_syntax = if let Some(path) = path {
|
||||
self.get_syntax_for_path(
|
||||
PathAbs::new(path).map_or_else(|_| path.to_owned(), |p| p.as_path().to_path_buf()),
|
||||
mapping,
|
||||
)
|
||||
let absolute_path = path.and_then(|p| {
|
||||
PathAbs::new(p)
|
||||
.ok()
|
||||
.map(|abs| abs.as_path().to_path_buf())
|
||||
.or_else(|| Some(p.to_owned()))
|
||||
});
|
||||
|
||||
let path_syntax = if let Some(ref path) = absolute_path {
|
||||
self.get_syntax_for_path(path, mapping).or_else(|e| {
|
||||
// If syntax detection failed on the given path, retry with the
|
||||
// canonicalized path (which resolves symlinks). This handles
|
||||
// cases like `Aliases/0install -> ../Formula/zero-install.rb`
|
||||
// where the symlink name has no extension but the target does.
|
||||
// See #1001.
|
||||
if matches!(e, Error::UndetectedSyntax(_)) {
|
||||
if let Ok(resolved) = fs::canonicalize(path) {
|
||||
if resolved != *path {
|
||||
return match self.get_syntax_for_path(&resolved, mapping) {
|
||||
Ok(syntax) => Ok(syntax),
|
||||
Err(Error::UndetectedSyntax(_)) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e)
|
||||
})
|
||||
} else {
|
||||
Err(Error::UndetectedSyntax("[unknown]".into()))
|
||||
};
|
||||
|
||||
// If a path wasn't provided, or if path based syntax detection
|
||||
// above failed, we fall back to first-line syntax detection.
|
||||
match path_syntax {
|
||||
// If a path wasn't provided, or if path based syntax detection
|
||||
// above failed, we fall back to first-line syntax detection.
|
||||
Err(Error::UndetectedSyntax(path)) => self
|
||||
.get_first_line_syntax(&mut input.reader)?
|
||||
.ok_or(Error::UndetectedSyntax(path)),
|
||||
Err(Error::UndetectedSyntax(path)) => {
|
||||
if let Some(syntax_in_set) = self.get_first_line_syntax(&mut input.reader)? {
|
||||
Ok(syntax_in_set)
|
||||
} else if let Some(language) = fallback_syntax {
|
||||
self.find_syntax_by_token(language)?
|
||||
.ok_or_else(|| Error::UnknownSyntax(language.to_owned()))
|
||||
} else {
|
||||
Err(Error::UndetectedSyntax(path))
|
||||
}
|
||||
}
|
||||
_ => path_syntax,
|
||||
}
|
||||
}
|
||||
@@ -262,6 +292,24 @@ impl HighlightingAssets {
|
||||
.map(|syntax| SyntaxReferenceInSet { syntax, syntax_set }))
|
||||
}
|
||||
|
||||
fn find_syntax_by_hidden_file_name(
|
||||
&self,
|
||||
file_name: &OsStr,
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let Some(hidden_file_extension) = file_name
|
||||
.to_str()
|
||||
.and_then(|name| name.strip_prefix('.'))
|
||||
.filter(|name| !name.is_empty())
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// syntect stores `hidden_file_extensions` in the same extension list as
|
||||
// regular file extensions, but dotfiles must be queried without the
|
||||
// leading period.
|
||||
self.find_syntax_by_extension(Some(OsStr::new(hidden_file_extension)))
|
||||
}
|
||||
|
||||
fn find_syntax_by_token(&self, token: &str) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let syntax_set = self.get_syntax_set()?;
|
||||
Ok(syntax_set
|
||||
@@ -275,6 +323,9 @@ impl HighlightingAssets {
|
||||
ignored_suffixes: &IgnoredSuffixes,
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let mut syntax = self.find_syntax_by_extension(Some(file_name))?;
|
||||
if syntax.is_none() {
|
||||
syntax = self.find_syntax_by_hidden_file_name(file_name)?;
|
||||
}
|
||||
if syntax.is_none() {
|
||||
syntax =
|
||||
ignored_suffixes.try_with_stripped_suffix(file_name, |stripped_file_name| {
|
||||
@@ -395,11 +446,12 @@ mod tests {
|
||||
fn get_syntax_name(
|
||||
&self,
|
||||
language: Option<&str>,
|
||||
fallback_syntax: Option<&str>,
|
||||
input: &mut OpenedInput,
|
||||
mapping: &SyntaxMapping,
|
||||
) -> String {
|
||||
self.assets
|
||||
.get_syntax(language, input, mapping)
|
||||
.get_syntax(language, fallback_syntax, input, mapping)
|
||||
.map(|syntax_in_set| syntax_in_set.syntax.name.clone())
|
||||
.unwrap_or_else(|_| "!no syntax!".to_owned())
|
||||
}
|
||||
@@ -419,7 +471,7 @@ mod tests {
|
||||
let dummy_stdin: &[u8] = &[];
|
||||
let mut opened_input = input.open(dummy_stdin, None).unwrap();
|
||||
|
||||
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
|
||||
self.get_syntax_name(None, None, &mut opened_input, &self.syntax_mapping)
|
||||
}
|
||||
|
||||
fn syntax_for_file_with_content_os(&self, file_name: &OsStr, first_line: &str) -> String {
|
||||
@@ -429,7 +481,7 @@ mod tests {
|
||||
let dummy_stdin: &[u8] = &[];
|
||||
let mut opened_input = input.open(dummy_stdin, None).unwrap();
|
||||
|
||||
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
|
||||
self.get_syntax_name(None, None, &mut opened_input, &self.syntax_mapping)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -449,7 +501,7 @@ mod tests {
|
||||
let input = Input::stdin().with_name(Some(file_name));
|
||||
let mut opened_input = input.open(content, None).unwrap();
|
||||
|
||||
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
|
||||
self.get_syntax_name(None, None, &mut opened_input, &self.syntax_mapping)
|
||||
}
|
||||
|
||||
fn syntax_is_same_for_inputkinds(&self, file_name: &str, content: &str) -> bool {
|
||||
@@ -661,6 +713,55 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "build-assets")]
|
||||
#[test]
|
||||
fn syntax_detection_hidden_file_extensions() {
|
||||
let source_dir = TempDir::new().expect("creation of temporary source directory");
|
||||
let cache_dir = TempDir::new().expect("creation of temporary cache directory");
|
||||
let syntax_dir = source_dir.path().join("syntaxes");
|
||||
|
||||
std::fs::create_dir_all(&syntax_dir).expect("creation of syntax directory succeeds");
|
||||
std::fs::write(
|
||||
syntax_dir.join("HiddenFileExtension.sublime-syntax"),
|
||||
r#"%YAML 1.2
|
||||
---
|
||||
name: Hidden File Extension
|
||||
hidden_file_extensions:
|
||||
- testrc
|
||||
scope: source.hiddenfileextension
|
||||
|
||||
contexts:
|
||||
main:
|
||||
- match: .
|
||||
scope: source.hiddenfileextension
|
||||
"#,
|
||||
)
|
||||
.expect("custom syntax can be written");
|
||||
|
||||
build(
|
||||
source_dir.path(),
|
||||
false,
|
||||
false,
|
||||
cache_dir.path(),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
)
|
||||
.expect("custom assets can be built");
|
||||
|
||||
let test = SyntaxDetectionTest {
|
||||
assets: HighlightingAssets::from_cache(cache_dir.path())
|
||||
.expect("custom syntax cache can be loaded"),
|
||||
syntax_mapping: SyntaxMapping::new(),
|
||||
temp_dir: TempDir::new().expect("creation of temporary directory"),
|
||||
};
|
||||
|
||||
assert_eq!(test.syntax_for_file(".testrc"), "Hidden File Extension");
|
||||
assert_eq!(
|
||||
test.syntax_for_stdin_with_content(".testrc", b""),
|
||||
"Hidden File Extension"
|
||||
);
|
||||
assert!(test.syntax_is_same_for_inputkinds(".testrc", ""));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn syntax_detection_for_symlinked_file() {
|
||||
@@ -682,8 +783,35 @@ mod tests {
|
||||
let mut opened_input = input.open(dummy_stdin, None).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
test.get_syntax_name(None, &mut opened_input, &test.syntax_mapping),
|
||||
test.get_syntax_name(None, None, &mut opened_input, &test.syntax_mapping),
|
||||
"SSH Config"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn syntax_detection_for_symlinked_file_by_target_extension() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let test = SyntaxDetectionTest::new();
|
||||
|
||||
let formula_dir = test.temp_dir.path().join("Formula");
|
||||
std::fs::create_dir(&formula_dir).unwrap();
|
||||
let target = formula_dir.join("zero-install.rb");
|
||||
File::create(&target).unwrap();
|
||||
|
||||
let aliases_dir = test.temp_dir.path().join("Aliases");
|
||||
std::fs::create_dir(&aliases_dir).unwrap();
|
||||
let link = aliases_dir.join("0install");
|
||||
symlink(&target, &link).unwrap();
|
||||
|
||||
let input = Input::ordinary_file(&link);
|
||||
let dummy_stdin: &[u8] = &[];
|
||||
let mut opened_input = input.open(dummy_stdin, None).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
test.get_syntax_name(None, None, &mut opened_input, &test.syntax_mapping),
|
||||
"Ruby"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+18
-11
@@ -384,6 +384,10 @@ impl App {
|
||||
None
|
||||
}
|
||||
}),
|
||||
fallback_syntax: self
|
||||
.matches
|
||||
.get_one::<String>("fallback-syntax")
|
||||
.map(|s| s.as_str()),
|
||||
show_nonprintable: self.matches.get_flag("show-all"),
|
||||
nonprintable_notation: match self
|
||||
.matches
|
||||
@@ -399,27 +403,30 @@ impl App {
|
||||
Some("no-printing") => BinaryBehavior::NoPrinting,
|
||||
_ => unreachable!("other values for --binary are not allowed"),
|
||||
},
|
||||
wrapping_mode: if self.interactive_output || maybe_term_width.is_some() {
|
||||
if !self.matches.get_flag("chop-long-lines") {
|
||||
wrapping_mode: {
|
||||
if self.matches.get_flag("chop-long-lines") {
|
||||
WrappingMode::NoWrapping(true)
|
||||
} else {
|
||||
match self.matches.get_one::<String>("wrap").map(|s| s.as_str()) {
|
||||
Some("character") => WrappingMode::Character,
|
||||
Some("word") => WrappingMode::Word,
|
||||
Some("never") => WrappingMode::NoWrapping(true),
|
||||
Some("auto") | None => {
|
||||
if style_components.plain() && maybe_term_width.is_none() {
|
||||
WrappingMode::NoWrapping(false)
|
||||
if self.interactive_output || maybe_term_width.is_some() {
|
||||
if style_components.plain() && maybe_term_width.is_none() {
|
||||
WrappingMode::NoWrapping(false)
|
||||
} else {
|
||||
WrappingMode::Character
|
||||
}
|
||||
} else {
|
||||
WrappingMode::Character
|
||||
// We don't have the tty width when piping to another program.
|
||||
// There's no point in wrapping when this is the case.
|
||||
WrappingMode::NoWrapping(false)
|
||||
}
|
||||
}
|
||||
_ => unreachable!("other values for --wrap are not allowed"),
|
||||
}
|
||||
} else {
|
||||
WrappingMode::NoWrapping(true)
|
||||
}
|
||||
} else {
|
||||
// We don't have the tty width when piping to another program.
|
||||
// There's no point in wrapping when this is the case.
|
||||
WrappingMode::NoWrapping(false)
|
||||
},
|
||||
colored_output: self.matches.get_flag("force-colorization")
|
||||
|| match self.matches.get_one::<String>("color").map(|s| s.as_str()) {
|
||||
|
||||
+14
-3
@@ -120,6 +120,17 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
language names and file extensions.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("fallback-syntax")
|
||||
.long("fallback-syntax")
|
||||
.visible_alias("fallback-language")
|
||||
.help("Set a fallback language for undetected syntaxes.")
|
||||
.long_help(
|
||||
"Set a fallback language for syntax highlighting when auto-detection fails. \
|
||||
Unlike '--language', this is only used when no syntax could be detected from \
|
||||
filename, custom syntax mappings, or first-line detection.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("highlight-line")
|
||||
.long("highlight-line")
|
||||
@@ -211,11 +222,11 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
.long("wrap")
|
||||
.overrides_with("wrap")
|
||||
.value_name("mode")
|
||||
.value_parser(["auto", "never", "character"])
|
||||
.value_parser(["auto", "never", "character", "word"])
|
||||
.default_value("auto")
|
||||
.hide_default_value(true)
|
||||
.help("Specify the text-wrapping mode (*auto*, never, character).")
|
||||
.long_help("Specify the text-wrapping mode (*auto*, never, character). \
|
||||
.help("Specify the text-wrapping mode (*auto*, never, character, word).")
|
||||
.long_help("Specify the text-wrapping mode (*auto*, never, character, word). \
|
||||
The '--terminal-width' option can be used in addition to \
|
||||
control the output width."),
|
||||
)
|
||||
|
||||
+55
-4
@@ -2,7 +2,7 @@ use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::directories::PROJECT_DIRS;
|
||||
|
||||
@@ -104,18 +104,32 @@ pub fn generate_config_file() -> bat::error::Result<()> {
|
||||
pub fn get_args_from_config_file() -> Result<Vec<OsString>, shell_words::ParseError> {
|
||||
let mut config = String::new();
|
||||
|
||||
if let Ok(c) = fs::read_to_string(system_config_file()) {
|
||||
let system_config = system_config_file();
|
||||
let user_config = config_file();
|
||||
|
||||
if let Ok(c) = fs::read_to_string(&system_config) {
|
||||
config.push_str(&c);
|
||||
config.push('\n');
|
||||
}
|
||||
|
||||
if let Ok(c) = fs::read_to_string(config_file()) {
|
||||
config.push_str(&c);
|
||||
// Skip the user config if it resolves to the same file as the system config,
|
||||
// which can happen when BAT_CONFIG_DIR is set to e.g. "/etc/bat". See #3589.
|
||||
if !same_file(&system_config, &user_config) {
|
||||
if let Ok(c) = fs::read_to_string(&user_config) {
|
||||
config.push_str(&c);
|
||||
}
|
||||
}
|
||||
|
||||
get_args_from_str(&config)
|
||||
}
|
||||
|
||||
fn same_file(a: &Path, b: &Path) -> bool {
|
||||
match (fs::canonicalize(a), fs::canonicalize(b)) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => a == b,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_args_from_env_opts_var() -> Option<Result<Vec<OsString>, shell_words::ParseError>> {
|
||||
env::var("BAT_OPTS").ok().map(|s| get_args_from_str(&s))
|
||||
}
|
||||
@@ -214,3 +228,40 @@ fn comments() {
|
||||
get_args_from_str(config).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_file_identical_paths() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let file = dir.path().join("config");
|
||||
fs::write(&file, "").unwrap();
|
||||
assert!(same_file(&file, &file));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_file_different_paths() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let a = dir.path().join("a");
|
||||
let b = dir.path().join("b");
|
||||
fs::write(&a, "").unwrap();
|
||||
fs::write(&b, "").unwrap();
|
||||
assert!(!same_file(&a, &b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_file_nonexistent() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let a = dir.path().join("a");
|
||||
let b = dir.path().join("b");
|
||||
assert!(!same_file(&a, &b));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn same_file_via_symlink() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let original = dir.path().join("config");
|
||||
let link = dir.path().join("link");
|
||||
fs::write(&original, "").unwrap();
|
||||
std::os::unix::fs::symlink(&original, &link).unwrap();
|
||||
assert!(same_file(&original, &link));
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ pub struct Config<'a> {
|
||||
/// The explicitly configured language, if any
|
||||
pub language: Option<&'a str>,
|
||||
|
||||
/// The fallback syntax used when auto-detection fails
|
||||
pub fallback_syntax: Option<&'a str>,
|
||||
|
||||
/// Whether or not to show/replace non-printable characters like space, tab and newline.
|
||||
pub show_nonprintable: bool,
|
||||
|
||||
|
||||
+13
-3
@@ -105,6 +105,10 @@ impl OutputType {
|
||||
let resolved_path = match grep_cli::resolve_binary(&pager.bin) {
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
crate::bat_warning!(
|
||||
"Pager '{}' not found, outputting to stdout instead",
|
||||
pager.bin
|
||||
);
|
||||
return Ok(OutputType::stdout());
|
||||
}
|
||||
};
|
||||
@@ -174,7 +178,13 @@ impl OutputType {
|
||||
Ok(p.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.map(OutputType::Pager)
|
||||
.unwrap_or_else(|_| OutputType::stdout()))
|
||||
.unwrap_or_else(|_| {
|
||||
crate::bat_warning!(
|
||||
"Pager '{}' not found, outputting to stdout instead",
|
||||
&pager.bin
|
||||
);
|
||||
OutputType::stdout()
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn stdout() -> Self {
|
||||
@@ -215,8 +225,8 @@ impl Drop for OutputType {
|
||||
let _ = command.wait();
|
||||
}
|
||||
OutputType::BuiltinPager(ref mut pager) => {
|
||||
if pager.handle.is_some() {
|
||||
let _ = pager.handle.take().unwrap().join().unwrap();
|
||||
if let Some(handle) = pager.handle.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
}
|
||||
OutputType::Stdout(_) => (),
|
||||
|
||||
+56
-4
@@ -268,7 +268,12 @@ impl<'a> InteractivePrinter<'a> {
|
||||
const PLAIN_TEXT_SYNTAX: &str = "Plain Text";
|
||||
const MANPAGE_SYNTAX: &str = "Manpage";
|
||||
const COMMAND_HELP_SYNTAX: &str = "Command Help";
|
||||
match assets.get_syntax(config.language, input, &config.syntax_mapping) {
|
||||
match assets.get_syntax(
|
||||
config.language,
|
||||
config.fallback_syntax,
|
||||
input,
|
||||
&config.syntax_mapping,
|
||||
) {
|
||||
Ok(syntax_in_set) => (
|
||||
syntax_in_set.syntax.name == PLAIN_TEXT_SYNTAX,
|
||||
syntax_in_set.syntax.name == MANPAGE_SYNTAX
|
||||
@@ -781,11 +786,21 @@ impl Printer for InteractivePrinter<'_> {
|
||||
// Displayed width of line_buf
|
||||
let mut current_width = 0;
|
||||
|
||||
let word_wrap = matches!(self.config.wrapping_mode, WrappingMode::Word);
|
||||
|
||||
// For word wrapping, track last whitespace position.
|
||||
let mut last_ws_idx: Option<usize> = None;
|
||||
|
||||
for c in text.chars() {
|
||||
// calculate the displayed width for next character
|
||||
let cw = c.width().unwrap_or(0);
|
||||
current_width += cw;
|
||||
|
||||
// Track whitespace positions for word wrapping.
|
||||
if word_wrap && c.is_whitespace() {
|
||||
last_ws_idx = Some(line_buf.len());
|
||||
}
|
||||
|
||||
// if next character cannot be printed on this line,
|
||||
// flush the buffer.
|
||||
if current_width > max_width {
|
||||
@@ -807,13 +822,37 @@ impl Printer for InteractivePrinter<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the break point and remainder
|
||||
// for word wrapping.
|
||||
let (emit_end, rest_start) = if word_wrap {
|
||||
if let Some(ws_idx) = last_ws_idx {
|
||||
// Skip the whitespace character itself
|
||||
// and carry the rest to the next line.
|
||||
let rs = ws_idx
|
||||
+ line_buf[ws_idx..]
|
||||
.chars()
|
||||
.next()
|
||||
.map(|ch| ch.len_utf8())
|
||||
.unwrap_or(0);
|
||||
(ws_idx, Some(rs))
|
||||
} else {
|
||||
(line_buf.len(), None)
|
||||
}
|
||||
} else {
|
||||
(line_buf.len(), None)
|
||||
};
|
||||
|
||||
// It wraps.
|
||||
write!(
|
||||
handle,
|
||||
"{}{}\n{}",
|
||||
as_terminal_escaped(
|
||||
style,
|
||||
&format!("{}{line_buf}", self.ansi_style),
|
||||
&format!(
|
||||
"{}{}",
|
||||
self.ansi_style,
|
||||
&line_buf[..emit_end]
|
||||
),
|
||||
self.config.true_color,
|
||||
self.config.colored_output,
|
||||
self.config.use_italic_text,
|
||||
@@ -826,8 +865,21 @@ impl Printer for InteractivePrinter<'_> {
|
||||
cursor = 0;
|
||||
max_width = cursor_max;
|
||||
|
||||
line_buf.clear();
|
||||
current_width = cw;
|
||||
if let Some(rs) = rest_start {
|
||||
// Word wrap: carry remainder to next line.
|
||||
let remainder = line_buf[rs..].to_string();
|
||||
let rem_width: usize = remainder
|
||||
.chars()
|
||||
.map(|ch| ch.width().unwrap_or(0))
|
||||
.sum();
|
||||
line_buf.clear();
|
||||
line_buf.push_str(&remainder);
|
||||
current_width = rem_width + cw;
|
||||
} else {
|
||||
line_buf.clear();
|
||||
current_width = cw;
|
||||
}
|
||||
last_ws_idx = None;
|
||||
}
|
||||
|
||||
line_buf.push(c);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WrappingMode {
|
||||
Character,
|
||||
Word,
|
||||
// The bool specifies whether wrapping has been explicitly disabled by the user via --wrap=never
|
||||
NoWrapping(bool),
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
The quick brown fox jumps over the lazy dog and then runs away
|
||||
superlongwordthatdefinitelyexceedstheterminalwidthandshouldfallbacktocharacterwrapping
|
||||
short words here
|
||||
@@ -1425,6 +1425,21 @@ fn pager_failed_to_parse() {
|
||||
.stderr(predicate::str::contains("Could not parse pager command"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn pager_missing_warning() {
|
||||
bat()
|
||||
.env("BAT_PAGER", "nonexistent-pager-xyz-missing")
|
||||
.arg("--paging=always")
|
||||
.arg("test.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::contains("[bat warning]"))
|
||||
.stderr(predicate::str::contains("not found"))
|
||||
.stderr(predicate::str::contains("nonexistent-pager-xyz-missing"))
|
||||
.stdout(predicate::str::contains("hello world\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn env_var_bat_paging() {
|
||||
@@ -1442,6 +1457,7 @@ fn env_var_bat_paging() {
|
||||
#[test]
|
||||
fn basic_set_terminal_title() {
|
||||
bat()
|
||||
.env("BAT_PAGER", "cat")
|
||||
.arg("--paging=always")
|
||||
.arg("--set-terminal-title")
|
||||
.arg("test.txt")
|
||||
@@ -2454,6 +2470,121 @@ fn no_first_line_fallback_when_mapping_to_invalid_syntax() {
|
||||
.stderr(predicate::str::contains("unknown syntax: 'InvalidSyntax'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_syntax_is_used_when_no_syntax_is_detected() {
|
||||
let content = "# comment\nfoo=bar\n";
|
||||
|
||||
let fallback_output = bat()
|
||||
.arg("--color=always")
|
||||
.arg("--style=plain")
|
||||
.arg("--file-name=unknown.fallbacksyntax")
|
||||
.arg("--fallback-syntax=bash")
|
||||
.write_stdin(content)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
let explicit_output = bat()
|
||||
.arg("--color=always")
|
||||
.arg("--style=plain")
|
||||
.arg("--language=bash")
|
||||
.arg("--file-name=unknown.fallbacksyntax")
|
||||
.write_stdin(content)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
assert_eq!(
|
||||
from_utf8(&fallback_output).expect("output is valid utf-8"),
|
||||
from_utf8(&explicit_output).expect("output is valid utf-8")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_syntax_does_not_override_detected_syntax() {
|
||||
let content = "fn main() { println!(\"hello\"); }\n";
|
||||
|
||||
let with_fallback = bat()
|
||||
.arg("--color=always")
|
||||
.arg("--style=plain")
|
||||
.arg("--file-name=test.rs")
|
||||
.arg("--fallback-syntax=json")
|
||||
.write_stdin(content)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
let without_fallback = bat()
|
||||
.arg("--color=always")
|
||||
.arg("--style=plain")
|
||||
.arg("--file-name=test.rs")
|
||||
.write_stdin(content)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
assert_eq!(
|
||||
from_utf8(&with_fallback).expect("output is valid utf-8"),
|
||||
from_utf8(&without_fallback).expect("output is valid utf-8")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_syntax_does_not_override_explicit_language() {
|
||||
let content = "{\"a\": 1}\n";
|
||||
|
||||
let with_fallback = bat()
|
||||
.arg("--color=always")
|
||||
.arg("--style=plain")
|
||||
.arg("--language=json")
|
||||
.arg("--fallback-syntax=rust")
|
||||
.arg("--file-name=unknown.fallbacksyntax")
|
||||
.write_stdin(content)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
let without_fallback = bat()
|
||||
.arg("--color=always")
|
||||
.arg("--style=plain")
|
||||
.arg("--language=json")
|
||||
.arg("--file-name=unknown.fallbacksyntax")
|
||||
.write_stdin(content)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
|
||||
assert_eq!(
|
||||
from_utf8(&with_fallback).expect("output is valid utf-8"),
|
||||
from_utf8(&without_fallback).expect("output is valid utf-8")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_fallback_syntax_returns_error() {
|
||||
bat()
|
||||
.arg("--color=always")
|
||||
.arg("--style=plain")
|
||||
.arg("--file-name=unknown.fallbacksyntax")
|
||||
.arg("--fallback-syntax=InvalidSyntax")
|
||||
.write_stdin("foo\n")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("unknown syntax: 'InvalidSyntax'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_all_mode() {
|
||||
bat()
|
||||
@@ -2884,6 +3015,44 @@ fn no_wrapping_with_chop_long_lines() {
|
||||
wrapping_test("--chop-long-lines", false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn wrap_never_flag_respected_with_paging_always() {
|
||||
mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
|
||||
bat()
|
||||
.arg("--pager=cat")
|
||||
.arg("--paging=always")
|
||||
.arg("--wrap=never")
|
||||
.arg("--color=never")
|
||||
.arg("--decorations=never")
|
||||
.arg("--style=plain")
|
||||
.write_stdin("abcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyz\n")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("abcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyz").normalize())
|
||||
.stderr("");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn s_flag_respected_with_paging_always() {
|
||||
mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
|
||||
bat()
|
||||
.arg("--pager=cat")
|
||||
.arg("--paging=always")
|
||||
.arg("-S")
|
||||
.arg("--color=never")
|
||||
.arg("--decorations=never")
|
||||
.arg("--style=plain")
|
||||
.write_stdin("abcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyz\n")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("abcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyz").normalize())
|
||||
.stderr("");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn theme_arg_overrides_env() {
|
||||
bat()
|
||||
@@ -3737,3 +3906,62 @@ fn unbuffered_mode_plain_output() {
|
||||
.success()
|
||||
.stdout("hello world\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_wrap_breaks_at_word_boundaries() {
|
||||
bat()
|
||||
.arg("word-wrap.txt")
|
||||
.arg("--wrap=word")
|
||||
.arg("--terminal-width=40")
|
||||
.arg("--style=plain")
|
||||
.arg("--decorations=always")
|
||||
.arg("--color=never")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
"\
|
||||
The quick brown fox jumps over the lazy
|
||||
dog and then runs away
|
||||
superlongwordthatdefinitelyexceedstheter
|
||||
minalwidthandshouldfallbacktocharacterwr
|
||||
apping
|
||||
short words here
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_wrap_with_line_numbers() {
|
||||
bat()
|
||||
.arg("word-wrap.txt")
|
||||
.arg("--wrap=word")
|
||||
.arg("--terminal-width=40")
|
||||
.arg("--style=numbers")
|
||||
.arg("--decorations=always")
|
||||
.arg("--color=never")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
" 1 The quick brown fox jumps over the
|
||||
lazy dog and then runs away
|
||||
2 superlongwordthatdefinitelyexceedst
|
||||
heterminalwidthandshouldfallbacktoc
|
||||
haracterwrapping
|
||||
3 short words here
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_wrap_short_line_no_wrap() {
|
||||
bat()
|
||||
.arg("--wrap=word")
|
||||
.arg("--terminal-width=80")
|
||||
.arg("--style=plain")
|
||||
.arg("--decorations=always")
|
||||
.arg("--color=never")
|
||||
.arg("single-line.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("Single Line\n");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
[38;2;190;132;255mUWSM[0m[38;2;249;38;114m([0m[38;2;230;219;116m1[0m[38;2;249;38;114m)[0m[38;2;248;248;242m [0m[38;2;253;151;31mGeneral[0m[38;2;253;151;31m [0m[38;2;253;151;31mCommands[0m[38;2;253;151;31m [0m[38;2;253;151;31mManual[0m[38;2;248;248;242m [0m[38;2;190;132;255mUWSM[0m[38;2;249;38;114m([0m[38;2;230;219;116m1[0m[38;2;249;38;114m)[0m
|
||||
|
||||
[38;2;253;151;31mNAME[0m
|
||||
[38;2;248;248;242m UWSM - Universal Wayland Session Manager.[0m
|
||||
|
||||
[38;2;253;151;31mSYNOPSIS[0m
|
||||
[38;2;248;248;242m uwsm [0m[38;2;248;248;242m[[0m[38;2;166;226;46m-h[0m[38;2;249;38;114m|[0m[38;2;166;226;46m-v[0m[38;2;248;248;242m][0m[38;2;248;248;242m {subcommand} [0m[38;2;248;248;242m[[0m[38;2;248;248;242moptions ...[0m[38;2;248;248;242m][0m
|
||||
|
||||
[38;2;253;151;31mDESCRIPTION[0m
|
||||
[38;2;248;248;242m Launches arbitrary wayland compositor via a set of systemd user units to provide graphical user[0m
|
||||
[38;2;248;248;242m session with environment management, XDG autostart support, clean shutdown. Provides helpers[0m
|
||||
[38;2;248;248;242m for launching applications as scopes or services.[0m
|
||||
|
||||
[38;2;253;151;31mSUBCOMMANDS[0m
|
||||
[38;2;248;248;242m select Select default compositor Entry.[0m
|
||||
[38;2;248;248;242m start Start compositor and graphical session.[0m
|
||||
[38;2;248;248;242m finalize Send compositor-set variables and unit startup notification to systemd user manager.[0m
|
||||
[38;2;248;248;242m stop Stop graphical session and compositor.[0m
|
||||
[38;2;248;248;242m app Application unit launcher (with Desktop Entry support).[0m
|
||||
[38;2;248;248;242m check Perform state checks (for scripting and info).[0m
|
||||
[38;2;248;248;242m aux Technical functions for use inside units.[0m
|
||||
|
||||
[38;2;248;248;242m See corresponding SUBCOMMANDS subsections below for further info.[0m
|
||||
|
||||
[38;2;248;248;242m Help for each subcommand is accessible by running "uwsm {subcommand} [0m[38;2;166;226;46m-h[0m[38;2;248;248;242m".[0m
|
||||
|
||||
[38;2;253;151;31mCONFIGURATION[0m
|
||||
[38;2;248;248;242m Files[0m
|
||||
[38;2;248;248;242m In XDG config hierarchy:[0m
|
||||
[38;2;248;248;242m uwsm/env[0m
|
||||
[38;2;248;248;242m uwsm/env.d/*[0m
|
||||
[38;2;248;248;242m uwsm/env-${compositor}[0m
|
||||
[38;2;248;248;242m uwsm/env-${compositor}.d/* Environment (shell) to be sourced for the graphical session.[0m
|
||||
[38;2;248;248;242m Sourced from directories of increasing priority, in each directory[0m
|
||||
[38;2;248;248;242m common file is sourced first, then suffixed files in the order of[0m
|
||||
[38;2;248;248;242m items listed in XDG_CURRENT_SESSION var (lowercased).[0m
|
||||
[38;2;248;248;242m uwsm/default-id Stores Desktop Entry ID of default compositor.[0m
|
||||
|
||||
[38;2;248;248;242m Fallback is also extended into the system part of XDG data hierarchy, this can be used for dis‐[0m
|
||||
[38;2;248;248;242m tro level defaults.[0m
|
||||
|
||||
[38;2;248;248;242m Environment vars[0m
|
||||
[38;2;248;248;242m UWSM_UNIT_RUNG (run|home)[0m
|
||||
[38;2;248;248;242m Which rung of systemd/user/ hierarchy to manage generated unit[0m
|
||||
[38;2;248;248;242m and drop-in files in: [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_RUNTIME_DIR[0m[38;2;248;248;242m or [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_CONFIG_HOME[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m UWSM_TWEAKS (boolean value)[0m
|
||||
[38;2;248;248;242m Set to False to remove and not generate tweak drop-ins for[0m
|
||||
[38;2;248;248;242m other software.[0m
|
||||
[38;2;248;248;242m UWSM_FINALIZE_VARNAMES (whitespace-separated names of env vars)[0m
|
||||
[38;2;248;248;242m Additional variables for "uwsm finalize".[0m
|
||||
[38;2;248;248;242m UWSM_WAIT_VARNAMES (whitespace-separated names of env vars)[0m
|
||||
[38;2;248;248;242m Variables to wait for in activation environment before proceed‐[0m
|
||||
[38;2;248;248;242m ing to graphical session (in addition to WAYLAND_DISPLAY).[0m
|
||||
[38;2;248;248;242m UWSM_WAIT_VARNAMES_TIMEOUT (int value)[0m
|
||||
[38;2;248;248;242m Seconds to wait for variables to appear in activation environ‐[0m
|
||||
[38;2;248;248;242m ment. Essentially, startup timeout (default: 10).[0m
|
||||
[38;2;248;248;242m UWSM_WAIT_VARNAMES_SETTLETIME (float value)[0m
|
||||
[38;2;248;248;242m Seconds to pause after all expected vars found in activation[0m
|
||||
[38;2;248;248;242m environment (default: 0.2).[0m
|
||||
[38;2;248;248;242m UWSM_APP_UNIT_TYPE (scope|service)[0m
|
||||
[38;2;248;248;242m Default unit type for launching apps (default: scope).[0m
|
||||
[38;2;248;248;242m UWSM_SILENT_START (int or boolean value)[0m
|
||||
[38;2;248;248;242m True or 1 to inhibit stdout messages from "uwsm start". 2 to[0m
|
||||
[38;2;248;248;242m also inhibit warnings.[0m
|
||||
[38;2;248;248;242m DEBUG (int or boolean value)[0m
|
||||
[38;2;248;248;242m True or positive number to dump debug info to stderr.[0m
|
||||
|
||||
[38;2;253;151;31mOPERATION OVERVIEW[0m
|
||||
[38;2;248;248;242m Login Sequence Integration[0m
|
||||
[38;2;248;248;242m uwsm can be launched by using conditional exec in shell profile to replace login shell (see[0m
|
||||
[38;2;248;248;242m Shell Profile Integration section).[0m
|
||||
|
||||
[38;2;248;248;242m Alternatively "uwsm start ..." command can be put into wayland session's Desktop Entry to be[0m
|
||||
[38;2;248;248;242m launched by a display manager (see Use Inside Desktop Entry section).[0m
|
||||
|
||||
[38;2;248;248;242m Compositor Selection[0m
|
||||
[38;2;248;248;242m uwsm can run arbitrary compositor command line or a Desktop Entry by ID (specifying Action ID[0m
|
||||
[38;2;248;248;242m is also supported).[0m
|
||||
|
||||
[38;2;248;248;242m Desktop Entry can also be selected via a whiptail menu (see select subcommand section).[0m
|
||||
|
||||
[38;2;248;248;242m Startup[0m
|
||||
[38;2;248;248;242m See start subcommand section for command syntax.[0m
|
||||
|
||||
[38;2;248;248;242m UWSM uses a set of units bound to standard user session targets:[0m
|
||||
|
||||
[38;2;248;248;242m • wayland-session-pre@.target (bound to graphical-session-pre.target)[0m
|
||||
[38;2;248;248;242m • wayland-wm-env@.service (environment preloader service)[0m
|
||||
[38;2;248;248;242m • wayland-session@.target (bound to graphical-session.target)[0m
|
||||
[38;2;248;248;242m • wayland-wm@.service (service for the selected compositor)[0m
|
||||
[38;2;248;248;242m • wayland-session-xdg-autostart@.target (bound to xdg-desktop-autostart.target)[0m
|
||||
[38;2;248;248;242m • wayland-session-envelope@.target (lives through entire lifecycle)[0m
|
||||
[38;2;248;248;242m • wayland-session-shutdown.target (conflicts with targets above for shutdown)[0m
|
||||
[38;2;248;248;242m • wayland-session-bindpid@.service (PID-tracking session killswitch)[0m
|
||||
[38;2;248;248;242m • wayland-session-waitenv.service (delays graphical session until vars appear)[0m
|
||||
|
||||
[38;2;248;248;242m Compositor ID (Desktop Entry ID or executable name) becomes the specifier for all templated[0m
|
||||
[38;2;248;248;242m units.[0m
|
||||
|
||||
[38;2;248;248;242m At the stage of graphical-session-pre.target, the environment saved from "uwsm start" context[0m
|
||||
[38;2;248;248;242m is loaded (or POSIX shell profile is sourced), uwsm environment files are sourced. The delta is[0m
|
||||
[38;2;248;248;242m exported to the systemd and D-Bus activation environments by the environment preloader service[0m
|
||||
[38;2;248;248;242m and is marked for cleanup at shutdown stage. Preloader shell context for convenience has[0m
|
||||
[38;2;248;248;242m IN_UWSM_ENV_PRELOADER var set to true.[0m
|
||||
|
||||
[38;2;248;248;242m At the stage of graphical-session.target (before it) the main compositor unit wayland-[0m
|
||||
[38;2;248;248;242m wm@${ID}.service and wayland-session-waitenv.service are started.[0m
|
||||
|
||||
[38;2;248;248;242m Compositor should at least put WAYLAND_DISPLAY variable to systemd activation environment. This[0m
|
||||
[38;2;248;248;242m will trigger uwsm's automatic finalization logic. Without WAYLAND_DISPLAY in activation envi‐[0m
|
||||
[38;2;248;248;242m ronment startup will timeout in 10 seconds.[0m
|
||||
|
||||
[38;2;248;248;242m Manual finalization is possible by running "uwsm finalize" (see finalize subcommand section),[0m
|
||||
[38;2;248;248;242m also in combination with tweaking UWSM_WAIT_VARNAMES and UWSM_WAIT_VARNAMES_SETTLETIME vars[0m
|
||||
[38;2;248;248;242m (see Environment vars section).[0m
|
||||
|
||||
[38;2;248;248;242m Successful activation of compositor unit and existence of WAYLAND_DISPLAY in activation envi‐[0m
|
||||
[38;2;248;248;242m ronment will allow graphical-session.target to be declared reached.[0m
|
||||
|
||||
[38;2;248;248;242m Finally, xdg-desktop-autostart.target is activated.[0m
|
||||
|
||||
[38;2;248;248;242m Inside session[0m
|
||||
[38;2;248;248;242m It is highly recommended to configure the compositor or app launcher to launch apps as scopes[0m
|
||||
[38;2;248;248;242m or services in special user session slices (app.slice, background.slice, session.slice). uwsm[0m
|
||||
[38;2;248;248;242m provides custom nested slices for apps to live in and be terminated on session end:[0m
|
||||
[38;2;248;248;242m • app-graphical.slice[0m
|
||||
[38;2;248;248;242m • background-graphical.slice[0m
|
||||
[38;2;248;248;242m • session-graphical.slice[0m
|
||||
|
||||
[38;2;248;248;242m A helper app subcommand is provided to handle all the systemd-run invocations for you (see app[0m
|
||||
[38;2;248;248;242m subcommand section).[0m
|
||||
|
||||
[38;2;248;248;242m The compositor is launched in session.slice by default (as recommended by [0m[38;2;166;226;46msystemd.[0m[38;2;166;226;46mspecial[0m[38;2;249;38;114m([0m[38;2;190;132;255m7[0m[38;2;249;38;114m)[0m[38;2;248;248;242m).[0m
|
||||
|
||||
[38;2;248;248;242m Shutdown[0m
|
||||
[38;2;248;248;242m Can be initiated by either:[0m
|
||||
[38;2;248;248;242m • running uwsm stop[0m
|
||||
[38;2;248;248;242m • stopping wayland-wm@*.service or wayland-session-envelope@*.target[0m
|
||||
[38;2;248;248;242m • starting wayland-session-shutdown.target[0m
|
||||
|
||||
[38;2;248;248;242m Systemd stops all user units in reverse, as it usually does. During deactivation of graphical-[0m
|
||||
[38;2;248;248;242m session-pre.target, the environment preloader service cleans activation environments by unset‐[0m
|
||||
[38;2;248;248;242m ting all variables that were marked for removal during startup and finalization stages.[0m
|
||||
|
||||
[38;2;248;248;242m Do not use compositor's native exit mechanism or kill its process directly.[0m
|
||||
|
||||
[38;2;253;151;31mSUBCOMMANDS[0m
|
||||
[38;2;248;248;242m select[0m
|
||||
[38;2;248;248;242m Selects default wayland session compositor Desktop Entry.[0m
|
||||
|
||||
[38;2;248;248;242m uwsm select[0m
|
||||
|
||||
[38;2;248;248;242m Invokes a whiptail menu to select default session among Desktop Entries in wayland-sessions XDG[0m
|
||||
[38;2;248;248;242m data hierarchy. Writes to ${XDG_CONFIG_HOME}/uwsm/default-id. Nothing else is done. Returns 1[0m
|
||||
[38;2;248;248;242m if selection is cancelled. Can be used for scripting launch condition in shell profile.[0m
|
||||
|
||||
[38;2;248;248;242m check[0m
|
||||
[38;2;248;248;242m Performs tests, returns 0 on success, 1 on failure.[0m
|
||||
|
||||
[38;2;248;248;242m is-active:[0m
|
||||
|
||||
[38;2;248;248;242m uwsm check is-active [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-v[0m[38;2;248;248;242m] [compositor][0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-v[0m[38;2;248;248;242m show additional info[0m
|
||||
[38;2;248;248;242m compositor check for specific compositor[0m
|
||||
|
||||
[38;2;248;248;242m Checks if unit of specific compositor or graphical-session*.target in general is in active or[0m
|
||||
[38;2;248;248;242m activating state.[0m
|
||||
|
||||
[38;2;248;248;242m may-start:[0m
|
||||
|
||||
[38;2;248;248;242m uwsm check may-start [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-g[0m[38;2;248;248;242m [S]] [[0m[38;2;166;226;46m-v[0m[38;2;248;248;242m|[0m[38;2;166;226;46m-q[0m[38;2;248;248;242m] [N ...][0m
|
||||
|
||||
[38;2;248;248;242m N ... allowed VT numbers (default: 1)[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-g[0m[38;2;248;248;242m S wait S seconds for graphical.target in queue (default: 60; 0 or less disables[0m
|
||||
[38;2;248;248;242m check).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-i[0m[38;2;248;248;242m do not check for login shell[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-r[0m[38;2;248;248;242m do not check for local session (allow remote session)[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-v[0m[38;2;248;248;242m show all failed tests[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-q[0m[38;2;248;248;242m be quiet[0m
|
||||
|
||||
[38;2;248;248;242m Checks whether it is OK to launch a wayland session via the following conditions:[0m
|
||||
[38;2;248;248;242m • DBUS_SESSION_BUS_ADDRESS is set[0m
|
||||
[38;2;248;248;242m • Running from login shell[0m
|
||||
[38;2;248;248;242m • System is at graphical.target[0m
|
||||
[38;2;248;248;242m • User graphical-session*.target units are not yet active[0m
|
||||
[38;2;248;248;242m • Foreground VT is among allowed (default: 1)[0m
|
||||
[38;2;248;248;242m • Login session's VT is matching[0m
|
||||
|
||||
[38;2;248;248;242m start[0m
|
||||
[38;2;248;248;242m Generates units for given compositor command line or Desktop Entry and starts them.[0m
|
||||
|
||||
[38;2;248;248;242m uwsm start [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-D[0m[38;2;248;248;242m name[:name...]] [[0m[38;2;166;226;46m-a[0m[38;2;248;248;242m|[0m[38;2;166;226;46m-e[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-N[0m[38;2;248;248;242m Name] [[0m[38;2;166;226;46m-C[0m[38;2;248;248;242m Comment] [[0m[38;2;166;226;46m-U[0m[38;2;248;248;242m {run|home}] [[0m[38;2;166;226;46m-t[0m[38;2;248;248;242m][0m
|
||||
[38;2;248;248;242m [[0m[38;2;166;226;46m-o[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-n[0m[38;2;248;248;242m] -- compositor [args ...][0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-F[0m[38;2;248;248;242m Hardcode mode, always write command line to unit drop-ins and use full[0m
|
||||
[38;2;248;248;242m paths.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-D[0m[38;2;248;248;242m name[:name...] Names to fill XDG_CURRENT_DESKTOP with (:[0m[38;2;166;226;46m-separated[0m[38;2;248;248;242m). Existing var con‐[0m
|
||||
[38;2;248;248;242m tent is a starting point if no active session is running.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-a[0m[38;2;248;248;242m Append desktop names set by [0m[38;2;166;226;46m-D[0m[38;2;248;248;242m to other sources (default).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-e[0m[38;2;248;248;242m Use desktop names set by [0m[38;2;166;226;46m-D[0m[38;2;248;248;242m exclusively, discard other sources.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-N[0m[38;2;248;248;242m Name Fancy name for compositor (filled from Desktop Entry by default).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-C[0m[38;2;248;248;242m Comment Fancy description for compositor (filled from Desktop Entry by de‐[0m
|
||||
[38;2;248;248;242m fault).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-U[0m[38;2;248;248;242m {run|home} Select rung for generated unit files: run: [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_RUNTIME_DIR[0m[38;2;248;248;242m/sys‐[0m
|
||||
[38;2;248;248;242m temd/user (default), or home: [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_CONFIG_HOME[0m[38;2;248;248;242m/systemd/user. Permanent[0m
|
||||
[38;2;248;248;242m destination will save some time by removing need for reloading systemd.[0m
|
||||
[38;2;248;248;242m Managed files from other rung will be removed. Can be preset with[0m
|
||||
[38;2;248;248;242m UWSM_UNIT_RUNG environment var.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m Do not generate (and remove) tweak unit files. Can be preset with[0m
|
||||
[38;2;248;248;242m UWSM_TWEAKS=false environment var.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-T[0m[38;2;248;248;242m Generate tweak unit files for other software. This is default behavior.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-g[0m[38;2;248;248;242m S Wait for S seconds for system graphical.target in queue and warn if[0m
|
||||
[38;2;248;248;242m timed out or not in queue (default: 60, negative to disable).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-G[0m[38;2;248;248;242m S Wait for S seconds for system graphical.target in queue and abort if[0m
|
||||
[38;2;248;248;242m timed out or not in queue (overrides [0m[38;2;166;226;46m-g[0m[38;2;248;248;242m, default: [0m[38;2;166;226;46m-1[0m[38;2;248;248;242m, (disabled)).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-o[0m[38;2;248;248;242m Only generate units, but do not start.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-n[0m[38;2;248;248;242m Dry run, do not write or start anything.[0m
|
||||
|
||||
[38;2;248;248;242m The first argument of the compositor command line acts as an ID and should be either one of:[0m
|
||||
[38;2;248;248;242m • Executable name[0m
|
||||
[38;2;248;248;242m • Desktop Entry ID (optionally with ":"[0m[38;2;166;226;46m-delimited[0m[38;2;248;248;242m action ID)[0m
|
||||
[38;2;248;248;242m • Special value:[0m
|
||||
[38;2;248;248;242m • select - invoke menu to select compositor.[0m
|
||||
[38;2;248;248;242m • default - run previously selected compositor (or select if no selection was saved).[0m
|
||||
|
||||
[38;2;248;248;242m If given as path, hardcode mode will be used implicitly.[0m
|
||||
|
||||
[38;2;248;248;242m Always use "--" to disambiguate dashed arguments intended for compositor itself.[0m
|
||||
|
||||
[38;2;248;248;242m After units are (re)generated, wayland-session-bindpid@${PID}.service is started, to track the[0m
|
||||
[38;2;248;248;242m PID of invoking uwsm, then uwsm process replaces itself with systemctl execution that starts[0m
|
||||
[38;2;248;248;242m wayland-wm@${ID}.service and waits for it to finish.[0m
|
||||
|
||||
[38;2;248;248;242m In order to complete the startup sequence, the compositor has to put WAYLAND_DISPLAY into the[0m
|
||||
[38;2;248;248;242m systemd activation environment. This can be done explicitly by making compositor run "uwsm fi‐[0m
|
||||
[38;2;248;248;242m nalize" command (see the next subsection).[0m
|
||||
|
||||
[38;2;248;248;242m finalize[0m
|
||||
[38;2;248;248;242m For running by a compositor on startup.[0m
|
||||
|
||||
[38;2;248;248;242m uwsm finalize [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [VAR_NAME ...][0m
|
||||
|
||||
[38;2;248;248;242m Exports WAYLAND_DISPLAY, DISPLAY and any defined vars mentioned by names in arguments or in[0m
|
||||
[38;2;248;248;242m UWSM_FINALIZE_VARNAMES variable (whitespace-separated). Then sends startup notification for the[0m
|
||||
[38;2;248;248;242m unit to systemd user manager.[0m
|
||||
|
||||
[38;2;248;248;242m This is required if compositor itself does not put WAYLAND_DISPLAY to systemd activation envi‐[0m
|
||||
[38;2;248;248;242m ronment, otherwise wayland-session@.service unit or a dedicated wayland-session-waitenv.service[0m
|
||||
[38;2;248;248;242m unit will terminate due to startup timeout.[0m
|
||||
|
||||
[38;2;248;248;242m UWSM_FINALIZE_VARNAMES variable can be prefilled by plugins.[0m
|
||||
|
||||
[38;2;248;248;242m Direct assignment as VAR_NAME=value is also possible, but recommended only for creating flags[0m
|
||||
[38;2;248;248;242m for UWSM_WAIT_VARNAMES mechanism.[0m
|
||||
|
||||
[38;2;248;248;242m stop[0m
|
||||
[38;2;248;248;242m Stops compositor and optionally removes generated units.[0m
|
||||
|
||||
[38;2;248;248;242m uwsm stop [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-r[0m[38;2;248;248;242m [compositor] [[0m[38;2;166;226;46m-U[0m[38;2;248;248;242m {run|home}] [[0m[38;2;166;226;46m-n[0m[38;2;248;248;242m][0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-r[0m[38;2;248;248;242m [compositor] Also remove units (all or only compositor-specific).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-U[0m[38;2;248;248;242m {run|home} Select rung for generated unit files: run: [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_RUNTIME_DIR[0m[38;2;248;248;242m/systemd/user[0m
|
||||
[38;2;248;248;242m (default), or home: [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_CONFIG_HOME[0m[38;2;248;248;242m/systemd/user. Permanent destination[0m
|
||||
[38;2;248;248;242m will save some time by removing need for reloading systemd. Managed files[0m
|
||||
[38;2;248;248;242m from other rung will be removed. Can be preset with UWSM_UNIT_RUNG envi‐[0m
|
||||
[38;2;248;248;242m ronment var.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-n[0m[38;2;248;248;242m Dry run, do not stop or remove anything.[0m
|
||||
|
||||
[38;2;248;248;242m app[0m
|
||||
[38;2;248;248;242m Application-to-unit launcher with Desktop Entry support.[0m
|
||||
|
||||
[38;2;248;248;242m uwsm app [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-s[0m[38;2;248;248;242m {a,b,s,custom.slice}] [[0m[38;2;166;226;46m-t[0m[38;2;248;248;242m {scope,service}] [[0m[38;2;166;226;46m-a[0m[38;2;248;248;242m app_name] [[0m[38;2;166;226;46m-u[0m[38;2;248;248;242m unit_name][0m
|
||||
[38;2;248;248;242m [[0m[38;2;166;226;46m-d[0m[38;2;248;248;242m unit_description] [[0m[38;2;166;226;46m-S[0m[38;2;248;248;242m ] [[0m[38;2;166;226;46m-T[0m[38;2;248;248;242m] -- application [args ...][0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-s[0m[38;2;248;248;242m {a,b,s,custom.slice} Slice selector (default: a):[0m
|
||||
[38;2;248;248;242m a - app-graphical.slice[0m
|
||||
[38;2;248;248;242m b - background-graphical.slice[0m
|
||||
[38;2;248;248;242m s - session-graphical.slice[0m
|
||||
[38;2;248;248;242m any slice by full name[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m {scope,service} Type of unit to launch (default: scope, can be preset by[0m
|
||||
[38;2;248;248;242m UWSM_APP_UNIT_TYPE env var).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-a[0m[38;2;248;248;242m app_name Override app name (a substring in unit name).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-u[0m[38;2;248;248;242m unit_name Override the whole autogenerated unit name.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-d[0m[38;2;248;248;242m unit_description Unit Description.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-p[0m[38;2;248;248;242m Property=value Set additional unit property (option is repeatable).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-S[0m[38;2;248;248;242m {out,err,both} Silence stdout, stderr, or both.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-T[0m[38;2;248;248;242m Launch app in a terminal. Allows command to be empty to just[0m
|
||||
[38;2;248;248;242m launch a terminal.[0m
|
||||
|
||||
[38;2;248;248;242m Application can be provided as a command with optional arguments, or a Desktop Entry ID, op‐[0m
|
||||
[38;2;248;248;242m tionally suffixed with ":"[0m[38;2;166;226;46m-delimited[0m[38;2;248;248;242m Action ID. If Desktop Entry is being launched, arguments[0m
|
||||
[38;2;248;248;242m should be compatible with it.[0m
|
||||
|
||||
[38;2;248;248;242m Always use "--" to disambiguate dashed arguments intended for application itself.[0m
|
||||
|
||||
[38;2;248;248;242m aux[0m
|
||||
[38;2;248;248;242m For use in systemd user services. Can only be called by systemd user manager.[0m
|
||||
|
||||
[38;2;248;248;242m prepare-env Prepares environment (for use in ExecStart in wayland-wm-env@.service bound to[0m
|
||||
[38;2;248;248;242m wayland-session-pre@.target).[0m
|
||||
[38;2;248;248;242m cleanup-env Cleans up environment (for use ExecStop in in wayland-wm-env@.service bound to[0m
|
||||
[38;2;248;248;242m wayland-session-pre@.target).[0m
|
||||
[38;2;248;248;242m exec Executes a command with arguments or a desktop entry (for use in Exec in wayland-[0m
|
||||
[38;2;248;248;242m wm@.service bound to wayland-session@.target).[0m
|
||||
[38;2;248;248;242m app-daemon Daemon for faster app argument generation, used by uwsm-app client.[0m
|
||||
|
||||
[38;2;253;151;31mAPP DAEMON[0m
|
||||
[38;2;248;248;242m Provided as wayland-wm-app-daemon.service to be started on-demand.[0m
|
||||
|
||||
[38;2;248;248;242m Daemon receives app arguments from ${XDG_RUNTIME_DIR}/uwsm-app-daemon-in pipe. Resulting argu‐[0m
|
||||
[38;2;248;248;242m ments are formatted as shell code and written to ${XDG_RUNTIME_DIR}/uwsm-app-daemon-out pipe.[0m
|
||||
|
||||
[38;2;248;248;242m Arguments are expected to be \0-delimited, leading \0 are stripped. One command is received per[0m
|
||||
[38;2;248;248;242m write+close.[0m
|
||||
|
||||
[38;2;248;248;242m The first argument determines the behavior:[0m
|
||||
|
||||
[38;2;248;248;242m • app the rest is processed the same as in "uwsm app"[0m
|
||||
[38;2;248;248;242m • ping just "pong" is returnedn[0m
|
||||
[38;2;248;248;242m • stop daemon is stoppedn[0m
|
||||
|
||||
[38;2;248;248;242m Single commands are prepended with exec, iterated commands are assembled with trailing & each,[0m
|
||||
[38;2;248;248;242m followed by wait.[0m
|
||||
|
||||
[38;2;248;248;242m The purpose of all this is to skip all the expensive Python startup and import routines that[0m
|
||||
[38;2;248;248;242m slow things down every time "uwsm app" is called. Instead the daemon does it once and then lis‐[0m
|
||||
[38;2;248;248;242m tens for requests, while a simple shell script may dump arguments to one pipe and run the code[0m
|
||||
[38;2;248;248;242m received from another via eval, which is much faster.[0m
|
||||
|
||||
[38;2;248;248;242m The simplest script is:[0m
|
||||
|
||||
[38;2;248;248;242m #!/bin/sh[0m
|
||||
[38;2;248;248;242m printf '0%s' app "$@" > "${XDG_RUNTIME_DIR}/uwsm-app-daemon-in"[0m
|
||||
[38;2;248;248;242m IFS='' read [0m[38;2;166;226;46m-r[0m[38;2;248;248;242m cmd < "${XDG_RUNTIME_DIR}/uwsm-app-daemon-out"[0m
|
||||
[38;2;248;248;242m eval "[0m[38;2;255;255;255m$[0m[38;2;190;132;255mcmd[0m[38;2;248;248;242m"[0m
|
||||
|
||||
[38;2;248;248;242m Provided uwsm-app client script is a bit smarter: it can start the daemon, applies timeouts,[0m
|
||||
[38;2;248;248;242m and supports newlines in returned args.[0m
|
||||
|
||||
[38;2;253;151;31mSHELL PROFILE INTEGRATION[0m
|
||||
[38;2;248;248;242m To launch uwsm automatically on login, add one of constructs below (or similar) to shell pro‐[0m
|
||||
[38;2;248;248;242m file.[0m
|
||||
|
||||
[38;2;248;248;242m This asks to select a compositor (or refuse and continue with login shell) when logged in on VT[0m
|
||||
[38;2;248;248;242m 1:[0m
|
||||
|
||||
[38;2;248;248;242m if uwsm check may-start && uwsm select; then[0m
|
||||
[38;2;248;248;242m exec systemd-cat [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m uwsm_start uwsm start default[0m
|
||||
[38;2;248;248;242m fi[0m
|
||||
|
||||
[38;2;248;248;242m This just starts a specific compositor depending on foreground VT:[0m
|
||||
|
||||
[38;2;248;248;242m if uwsm check may-start 1; then[0m
|
||||
[38;2;248;248;242m exec systemd-cat [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m uwsm_start uwsm start sway.desktop[0m
|
||||
[38;2;248;248;242m elif uwsm check may-start 2; then[0m
|
||||
[38;2;248;248;242m exec systemd-cat [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m uwsm_start uwsm start labwc.desktop[0m
|
||||
[38;2;248;248;242m fi[0m
|
||||
|
||||
[38;2;248;248;242m Using "uwsm check may-start" as a condition is essential, not only to prevent accidental[0m
|
||||
[38;2;248;248;242m startup attempts where they are not expected, but also since startup may involve sourcing shell[0m
|
||||
[38;2;248;248;242m profile, which might lead to nasty loops.[0m
|
||||
|
||||
[38;2;248;248;242m See check subcommand section for info on may-start checker.[0m
|
||||
|
||||
[38;2;248;248;242m exec allows uwsm to replace login shell in order to properly bind to user session and handle[0m
|
||||
[38;2;248;248;242m session termination.[0m
|
||||
|
||||
[38;2;248;248;242m "systemd-cat [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m uwsm_start" (optional) executes the command given to it (uwsm) with its stdout[0m
|
||||
[38;2;248;248;242m and stderr connected to the systemd journal, tagged with identifier "uwsm_start". See systemd-[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46mcat[0m[38;2;249;38;114m([0m[38;2;190;132;255m1[0m[38;2;249;38;114m)[0m[38;2;248;248;242m for more options.[0m
|
||||
|
||||
[38;2;253;151;31mUSE INSIDE DESKTOP ENTRY[0m
|
||||
[38;2;248;248;242m To launch uwsm from a display/login manager, "uwsm start" can be used inside Desktop Entries.[0m
|
||||
[38;2;248;248;242m Example /usr/local/share/wayland-sessions/my-compositor.desktop:[0m
|
||||
|
||||
[38;2;248;248;242m [Desktop Entry][0m
|
||||
[38;2;248;248;242m Name=My compositor (with UWSM)[0m
|
||||
[38;2;248;248;242m Comment=My cool compositor[0m
|
||||
[38;2;248;248;242m Exec=uwsm start [0m[38;2;166;226;46m-N[0m[38;2;248;248;242m "My compositor" [0m[38;2;166;226;46m-D[0m[38;2;248;248;242m mycompositor [0m[38;2;166;226;46m-C[0m[38;2;248;248;242m "My cool compositor" mywm[0m
|
||||
[38;2;248;248;242m DesktopNames=mycompositor[0m
|
||||
[38;2;248;248;242m Type=Application[0m
|
||||
|
||||
[38;2;248;248;242m Things to keep in mind:[0m
|
||||
|
||||
[38;2;248;248;242m • For consistency, command line arguments should mirror the keys of the entry[0m
|
||||
[38;2;248;248;242m • Command in Exec= should start with "uwsm start"[0m
|
||||
[38;2;248;248;242m • It should not point to itself (as a combination of Desktop Entry ID and Action ID)[0m
|
||||
[38;2;248;248;242m • It should not point to a Desktop Entry ID and Action ID that also uses ‘uwsm‘[0m
|
||||
|
||||
[38;2;248;248;242m Potentially such entries may be found and used by uwsm itself, i.e. in shell profile integra‐[0m
|
||||
[38;2;248;248;242m tion situation, or when launched manually. Following the principles above ensures uwsm will[0m
|
||||
[38;2;248;248;242m properly recognize itself and parse requested arguments inside the entry without any side ef‐[0m
|
||||
[38;2;248;248;242m fects.[0m
|
||||
|
||||
[38;2;253;151;31mSEE ALSO[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46muwsm-plugins[0m[38;2;249;38;114m([0m[38;2;190;132;255m3[0m[38;2;249;38;114m)[0m[38;2;248;248;242m, [0m[38;2;166;226;46msystemd-run[0m[38;2;249;38;114m([0m[38;2;190;132;255m1[0m[38;2;249;38;114m)[0m[38;2;248;248;242m, [0m[38;2;166;226;46msystemd-cat[0m[38;2;249;38;114m([0m[38;2;190;132;255m1[0m[38;2;249;38;114m)[0m[38;2;248;248;242m, [0m[38;2;166;226;46msystemd.[0m[38;2;166;226;46mspecial[0m[38;2;249;38;114m([0m[38;2;190;132;255m7[0m[38;2;249;38;114m)[0m
|
||||
|
||||
[38;2;248;248;242m 2026-02-14 [0m[38;2;166;226;46mUWSM[0m[38;2;249;38;114m([0m[38;2;190;132;255m1[0m[38;2;249;38;114m)[0m
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
UWSM(1) General Commands Manual UWSM(1)
|
||||
|
||||
NAME
|
||||
UWSM - Universal Wayland Session Manager.
|
||||
|
||||
SYNOPSIS
|
||||
uwsm [-h|-v] {subcommand} [options ...]
|
||||
|
||||
DESCRIPTION
|
||||
Launches arbitrary wayland compositor via a set of systemd user units to provide graphical user
|
||||
session with environment management, XDG autostart support, clean shutdown. Provides helpers
|
||||
for launching applications as scopes or services.
|
||||
|
||||
SUBCOMMANDS
|
||||
select Select default compositor Entry.
|
||||
start Start compositor and graphical session.
|
||||
finalize Send compositor-set variables and unit startup notification to systemd user manager.
|
||||
stop Stop graphical session and compositor.
|
||||
app Application unit launcher (with Desktop Entry support).
|
||||
check Perform state checks (for scripting and info).
|
||||
aux Technical functions for use inside units.
|
||||
|
||||
See corresponding SUBCOMMANDS subsections below for further info.
|
||||
|
||||
Help for each subcommand is accessible by running "uwsm {subcommand} -h".
|
||||
|
||||
CONFIGURATION
|
||||
Files
|
||||
In XDG config hierarchy:
|
||||
uwsm/env
|
||||
uwsm/env.d/*
|
||||
uwsm/env-${compositor}
|
||||
uwsm/env-${compositor}.d/* Environment (shell) to be sourced for the graphical session.
|
||||
Sourced from directories of increasing priority, in each directory
|
||||
common file is sourced first, then suffixed files in the order of
|
||||
items listed in XDG_CURRENT_SESSION var (lowercased).
|
||||
uwsm/default-id Stores Desktop Entry ID of default compositor.
|
||||
|
||||
Fallback is also extended into the system part of XDG data hierarchy, this can be used for dis‐
|
||||
tro level defaults.
|
||||
|
||||
Environment vars
|
||||
UWSM_UNIT_RUNG (run|home)
|
||||
Which rung of systemd/user/ hierarchy to manage generated unit
|
||||
and drop-in files in: $XDG_RUNTIME_DIR or $XDG_CONFIG_HOME.
|
||||
UWSM_TWEAKS (boolean value)
|
||||
Set to False to remove and not generate tweak drop-ins for
|
||||
other software.
|
||||
UWSM_FINALIZE_VARNAMES (whitespace-separated names of env vars)
|
||||
Additional variables for "uwsm finalize".
|
||||
UWSM_WAIT_VARNAMES (whitespace-separated names of env vars)
|
||||
Variables to wait for in activation environment before proceed‐
|
||||
ing to graphical session (in addition to WAYLAND_DISPLAY).
|
||||
UWSM_WAIT_VARNAMES_TIMEOUT (int value)
|
||||
Seconds to wait for variables to appear in activation environ‐
|
||||
ment. Essentially, startup timeout (default: 10).
|
||||
UWSM_WAIT_VARNAMES_SETTLETIME (float value)
|
||||
Seconds to pause after all expected vars found in activation
|
||||
environment (default: 0.2).
|
||||
UWSM_APP_UNIT_TYPE (scope|service)
|
||||
Default unit type for launching apps (default: scope).
|
||||
UWSM_SILENT_START (int or boolean value)
|
||||
True or 1 to inhibit stdout messages from "uwsm start". 2 to
|
||||
also inhibit warnings.
|
||||
DEBUG (int or boolean value)
|
||||
True or positive number to dump debug info to stderr.
|
||||
|
||||
OPERATION OVERVIEW
|
||||
Login Sequence Integration
|
||||
uwsm can be launched by using conditional exec in shell profile to replace login shell (see
|
||||
Shell Profile Integration section).
|
||||
|
||||
Alternatively "uwsm start ..." command can be put into wayland session's Desktop Entry to be
|
||||
launched by a display manager (see Use Inside Desktop Entry section).
|
||||
|
||||
Compositor Selection
|
||||
uwsm can run arbitrary compositor command line or a Desktop Entry by ID (specifying Action ID
|
||||
is also supported).
|
||||
|
||||
Desktop Entry can also be selected via a whiptail menu (see select subcommand section).
|
||||
|
||||
Startup
|
||||
See start subcommand section for command syntax.
|
||||
|
||||
UWSM uses a set of units bound to standard user session targets:
|
||||
|
||||
• wayland-session-pre@.target (bound to graphical-session-pre.target)
|
||||
• wayland-wm-env@.service (environment preloader service)
|
||||
• wayland-session@.target (bound to graphical-session.target)
|
||||
• wayland-wm@.service (service for the selected compositor)
|
||||
• wayland-session-xdg-autostart@.target (bound to xdg-desktop-autostart.target)
|
||||
• wayland-session-envelope@.target (lives through entire lifecycle)
|
||||
• wayland-session-shutdown.target (conflicts with targets above for shutdown)
|
||||
• wayland-session-bindpid@.service (PID-tracking session killswitch)
|
||||
• wayland-session-waitenv.service (delays graphical session until vars appear)
|
||||
|
||||
Compositor ID (Desktop Entry ID or executable name) becomes the specifier for all templated
|
||||
units.
|
||||
|
||||
At the stage of graphical-session-pre.target, the environment saved from "uwsm start" context
|
||||
is loaded (or POSIX shell profile is sourced), uwsm environment files are sourced. The delta is
|
||||
exported to the systemd and D-Bus activation environments by the environment preloader service
|
||||
and is marked for cleanup at shutdown stage. Preloader shell context for convenience has
|
||||
IN_UWSM_ENV_PRELOADER var set to true.
|
||||
|
||||
At the stage of graphical-session.target (before it) the main compositor unit wayland-
|
||||
wm@${ID}.service and wayland-session-waitenv.service are started.
|
||||
|
||||
Compositor should at least put WAYLAND_DISPLAY variable to systemd activation environment. This
|
||||
will trigger uwsm's automatic finalization logic. Without WAYLAND_DISPLAY in activation envi‐
|
||||
ronment startup will timeout in 10 seconds.
|
||||
|
||||
Manual finalization is possible by running "uwsm finalize" (see finalize subcommand section),
|
||||
also in combination with tweaking UWSM_WAIT_VARNAMES and UWSM_WAIT_VARNAMES_SETTLETIME vars
|
||||
(see Environment vars section).
|
||||
|
||||
Successful activation of compositor unit and existence of WAYLAND_DISPLAY in activation envi‐
|
||||
ronment will allow graphical-session.target to be declared reached.
|
||||
|
||||
Finally, xdg-desktop-autostart.target is activated.
|
||||
|
||||
Inside session
|
||||
It is highly recommended to configure the compositor or app launcher to launch apps as scopes
|
||||
or services in special user session slices (app.slice, background.slice, session.slice). uwsm
|
||||
provides custom nested slices for apps to live in and be terminated on session end:
|
||||
• app-graphical.slice
|
||||
• background-graphical.slice
|
||||
• session-graphical.slice
|
||||
|
||||
A helper app subcommand is provided to handle all the systemd-run invocations for you (see app
|
||||
subcommand section).
|
||||
|
||||
The compositor is launched in session.slice by default (as recommended by systemd.special(7)).
|
||||
|
||||
Shutdown
|
||||
Can be initiated by either:
|
||||
• running uwsm stop
|
||||
• stopping wayland-wm@*.service or wayland-session-envelope@*.target
|
||||
• starting wayland-session-shutdown.target
|
||||
|
||||
Systemd stops all user units in reverse, as it usually does. During deactivation of graphical-
|
||||
session-pre.target, the environment preloader service cleans activation environments by unset‐
|
||||
ting all variables that were marked for removal during startup and finalization stages.
|
||||
|
||||
Do not use compositor's native exit mechanism or kill its process directly.
|
||||
|
||||
SUBCOMMANDS
|
||||
select
|
||||
Selects default wayland session compositor Desktop Entry.
|
||||
|
||||
uwsm select
|
||||
|
||||
Invokes a whiptail menu to select default session among Desktop Entries in wayland-sessions XDG
|
||||
data hierarchy. Writes to ${XDG_CONFIG_HOME}/uwsm/default-id. Nothing else is done. Returns 1
|
||||
if selection is cancelled. Can be used for scripting launch condition in shell profile.
|
||||
|
||||
check
|
||||
Performs tests, returns 0 on success, 1 on failure.
|
||||
|
||||
is-active:
|
||||
|
||||
uwsm check is-active [-h] [-v] [compositor]
|
||||
|
||||
-v show additional info
|
||||
compositor check for specific compositor
|
||||
|
||||
Checks if unit of specific compositor or graphical-session*.target in general is in active or
|
||||
activating state.
|
||||
|
||||
may-start:
|
||||
|
||||
uwsm check may-start [-h] [-g [S]] [-v|-q] [N ...]
|
||||
|
||||
N ... allowed VT numbers (default: 1)
|
||||
-g S wait S seconds for graphical.target in queue (default: 60; 0 or less disables
|
||||
check).
|
||||
-i do not check for login shell
|
||||
-r do not check for local session (allow remote session)
|
||||
-v show all failed tests
|
||||
-q be quiet
|
||||
|
||||
Checks whether it is OK to launch a wayland session via the following conditions:
|
||||
• DBUS_SESSION_BUS_ADDRESS is set
|
||||
• Running from login shell
|
||||
• System is at graphical.target
|
||||
• User graphical-session*.target units are not yet active
|
||||
• Foreground VT is among allowed (default: 1)
|
||||
• Login session's VT is matching
|
||||
|
||||
start
|
||||
Generates units for given compositor command line or Desktop Entry and starts them.
|
||||
|
||||
uwsm start [-h] [-D name[:name...]] [-a|-e] [-N Name] [-C Comment] [-U {run|home}] [-t]
|
||||
[-o] [-n] -- compositor [args ...]
|
||||
|
||||
-F Hardcode mode, always write command line to unit drop-ins and use full
|
||||
paths.
|
||||
-D name[:name...] Names to fill XDG_CURRENT_DESKTOP with (:-separated). Existing var con‐
|
||||
tent is a starting point if no active session is running.
|
||||
-a Append desktop names set by -D to other sources (default).
|
||||
-e Use desktop names set by -D exclusively, discard other sources.
|
||||
-N Name Fancy name for compositor (filled from Desktop Entry by default).
|
||||
-C Comment Fancy description for compositor (filled from Desktop Entry by de‐
|
||||
fault).
|
||||
-U {run|home} Select rung for generated unit files: run: $XDG_RUNTIME_DIR/sys‐
|
||||
temd/user (default), or home: $XDG_CONFIG_HOME/systemd/user. Permanent
|
||||
destination will save some time by removing need for reloading systemd.
|
||||
Managed files from other rung will be removed. Can be preset with
|
||||
UWSM_UNIT_RUNG environment var.
|
||||
-t Do not generate (and remove) tweak unit files. Can be preset with
|
||||
UWSM_TWEAKS=false environment var.
|
||||
-T Generate tweak unit files for other software. This is default behavior.
|
||||
-g S Wait for S seconds for system graphical.target in queue and warn if
|
||||
timed out or not in queue (default: 60, negative to disable).
|
||||
-G S Wait for S seconds for system graphical.target in queue and abort if
|
||||
timed out or not in queue (overrides -g, default: -1, (disabled)).
|
||||
-o Only generate units, but do not start.
|
||||
-n Dry run, do not write or start anything.
|
||||
|
||||
The first argument of the compositor command line acts as an ID and should be either one of:
|
||||
• Executable name
|
||||
• Desktop Entry ID (optionally with ":"-delimited action ID)
|
||||
• Special value:
|
||||
• select - invoke menu to select compositor.
|
||||
• default - run previously selected compositor (or select if no selection was saved).
|
||||
|
||||
If given as path, hardcode mode will be used implicitly.
|
||||
|
||||
Always use "--" to disambiguate dashed arguments intended for compositor itself.
|
||||
|
||||
After units are (re)generated, wayland-session-bindpid@${PID}.service is started, to track the
|
||||
PID of invoking uwsm, then uwsm process replaces itself with systemctl execution that starts
|
||||
wayland-wm@${ID}.service and waits for it to finish.
|
||||
|
||||
In order to complete the startup sequence, the compositor has to put WAYLAND_DISPLAY into the
|
||||
systemd activation environment. This can be done explicitly by making compositor run "uwsm fi‐
|
||||
nalize" command (see the next subsection).
|
||||
|
||||
finalize
|
||||
For running by a compositor on startup.
|
||||
|
||||
uwsm finalize [-h] [VAR_NAME ...]
|
||||
|
||||
Exports WAYLAND_DISPLAY, DISPLAY and any defined vars mentioned by names in arguments or in
|
||||
UWSM_FINALIZE_VARNAMES variable (whitespace-separated). Then sends startup notification for the
|
||||
unit to systemd user manager.
|
||||
|
||||
This is required if compositor itself does not put WAYLAND_DISPLAY to systemd activation envi‐
|
||||
ronment, otherwise wayland-session@.service unit or a dedicated wayland-session-waitenv.service
|
||||
unit will terminate due to startup timeout.
|
||||
|
||||
UWSM_FINALIZE_VARNAMES variable can be prefilled by plugins.
|
||||
|
||||
Direct assignment as VAR_NAME=value is also possible, but recommended only for creating flags
|
||||
for UWSM_WAIT_VARNAMES mechanism.
|
||||
|
||||
stop
|
||||
Stops compositor and optionally removes generated units.
|
||||
|
||||
uwsm stop [-h] [-r [compositor] [-U {run|home}] [-n]
|
||||
|
||||
-r [compositor] Also remove units (all or only compositor-specific).
|
||||
-U {run|home} Select rung for generated unit files: run: $XDG_RUNTIME_DIR/systemd/user
|
||||
(default), or home: $XDG_CONFIG_HOME/systemd/user. Permanent destination
|
||||
will save some time by removing need for reloading systemd. Managed files
|
||||
from other rung will be removed. Can be preset with UWSM_UNIT_RUNG envi‐
|
||||
ronment var.
|
||||
-n Dry run, do not stop or remove anything.
|
||||
|
||||
app
|
||||
Application-to-unit launcher with Desktop Entry support.
|
||||
|
||||
uwsm app [-h] [-s {a,b,s,custom.slice}] [-t {scope,service}] [-a app_name] [-u unit_name]
|
||||
[-d unit_description] [-S ] [-T] -- application [args ...]
|
||||
|
||||
-s {a,b,s,custom.slice} Slice selector (default: a):
|
||||
a - app-graphical.slice
|
||||
b - background-graphical.slice
|
||||
s - session-graphical.slice
|
||||
any slice by full name
|
||||
-t {scope,service} Type of unit to launch (default: scope, can be preset by
|
||||
UWSM_APP_UNIT_TYPE env var).
|
||||
-a app_name Override app name (a substring in unit name).
|
||||
-u unit_name Override the whole autogenerated unit name.
|
||||
-d unit_description Unit Description.
|
||||
-p Property=value Set additional unit property (option is repeatable).
|
||||
-S {out,err,both} Silence stdout, stderr, or both.
|
||||
-T Launch app in a terminal. Allows command to be empty to just
|
||||
launch a terminal.
|
||||
|
||||
Application can be provided as a command with optional arguments, or a Desktop Entry ID, op‐
|
||||
tionally suffixed with ":"-delimited Action ID. If Desktop Entry is being launched, arguments
|
||||
should be compatible with it.
|
||||
|
||||
Always use "--" to disambiguate dashed arguments intended for application itself.
|
||||
|
||||
aux
|
||||
For use in systemd user services. Can only be called by systemd user manager.
|
||||
|
||||
prepare-env Prepares environment (for use in ExecStart in wayland-wm-env@.service bound to
|
||||
wayland-session-pre@.target).
|
||||
cleanup-env Cleans up environment (for use ExecStop in in wayland-wm-env@.service bound to
|
||||
wayland-session-pre@.target).
|
||||
exec Executes a command with arguments or a desktop entry (for use in Exec in wayland-
|
||||
wm@.service bound to wayland-session@.target).
|
||||
app-daemon Daemon for faster app argument generation, used by uwsm-app client.
|
||||
|
||||
APP DAEMON
|
||||
Provided as wayland-wm-app-daemon.service to be started on-demand.
|
||||
|
||||
Daemon receives app arguments from ${XDG_RUNTIME_DIR}/uwsm-app-daemon-in pipe. Resulting argu‐
|
||||
ments are formatted as shell code and written to ${XDG_RUNTIME_DIR}/uwsm-app-daemon-out pipe.
|
||||
|
||||
Arguments are expected to be \0-delimited, leading \0 are stripped. One command is received per
|
||||
write+close.
|
||||
|
||||
The first argument determines the behavior:
|
||||
|
||||
• app the rest is processed the same as in "uwsm app"
|
||||
• ping just "pong" is returnedn
|
||||
• stop daemon is stoppedn
|
||||
|
||||
Single commands are prepended with exec, iterated commands are assembled with trailing & each,
|
||||
followed by wait.
|
||||
|
||||
The purpose of all this is to skip all the expensive Python startup and import routines that
|
||||
slow things down every time "uwsm app" is called. Instead the daemon does it once and then lis‐
|
||||
tens for requests, while a simple shell script may dump arguments to one pipe and run the code
|
||||
received from another via eval, which is much faster.
|
||||
|
||||
The simplest script is:
|
||||
|
||||
#!/bin/sh
|
||||
printf '0%s' app "$@" > "${XDG_RUNTIME_DIR}/uwsm-app-daemon-in"
|
||||
IFS='' read -r cmd < "${XDG_RUNTIME_DIR}/uwsm-app-daemon-out"
|
||||
eval "$cmd"
|
||||
|
||||
Provided uwsm-app client script is a bit smarter: it can start the daemon, applies timeouts,
|
||||
and supports newlines in returned args.
|
||||
|
||||
SHELL PROFILE INTEGRATION
|
||||
To launch uwsm automatically on login, add one of constructs below (or similar) to shell pro‐
|
||||
file.
|
||||
|
||||
This asks to select a compositor (or refuse and continue with login shell) when logged in on VT
|
||||
1:
|
||||
|
||||
if uwsm check may-start && uwsm select; then
|
||||
exec systemd-cat -t uwsm_start uwsm start default
|
||||
fi
|
||||
|
||||
This just starts a specific compositor depending on foreground VT:
|
||||
|
||||
if uwsm check may-start 1; then
|
||||
exec systemd-cat -t uwsm_start uwsm start sway.desktop
|
||||
elif uwsm check may-start 2; then
|
||||
exec systemd-cat -t uwsm_start uwsm start labwc.desktop
|
||||
fi
|
||||
|
||||
Using "uwsm check may-start" as a condition is essential, not only to prevent accidental
|
||||
startup attempts where they are not expected, but also since startup may involve sourcing shell
|
||||
profile, which might lead to nasty loops.
|
||||
|
||||
See check subcommand section for info on may-start checker.
|
||||
|
||||
exec allows uwsm to replace login shell in order to properly bind to user session and handle
|
||||
session termination.
|
||||
|
||||
"systemd-cat -t uwsm_start" (optional) executes the command given to it (uwsm) with its stdout
|
||||
and stderr connected to the systemd journal, tagged with identifier "uwsm_start". See systemd-
|
||||
cat(1) for more options.
|
||||
|
||||
USE INSIDE DESKTOP ENTRY
|
||||
To launch uwsm from a display/login manager, "uwsm start" can be used inside Desktop Entries.
|
||||
Example /usr/local/share/wayland-sessions/my-compositor.desktop:
|
||||
|
||||
[Desktop Entry]
|
||||
Name=My compositor (with UWSM)
|
||||
Comment=My cool compositor
|
||||
Exec=uwsm start -N "My compositor" -D mycompositor -C "My cool compositor" mywm
|
||||
DesktopNames=mycompositor
|
||||
Type=Application
|
||||
|
||||
Things to keep in mind:
|
||||
|
||||
• For consistency, command line arguments should mirror the keys of the entry
|
||||
• Command in Exec= should start with "uwsm start"
|
||||
• It should not point to itself (as a combination of Desktop Entry ID and Action ID)
|
||||
• It should not point to a Desktop Entry ID and Action ID that also uses ‘uwsm‘
|
||||
|
||||
Potentially such entries may be found and used by uwsm itself, i.e. in shell profile integra‐
|
||||
tion situation, or when launched manually. Following the principles above ensures uwsm will
|
||||
properly recognize itself and parse requested arguments inside the entry without any side ef‐
|
||||
fects.
|
||||
|
||||
SEE ALSO
|
||||
uwsm-plugins(3), systemd-run(1), systemd-cat(1), systemd.special(7)
|
||||
|
||||
2026-02-14 UWSM(1)
|
||||
Reference in New Issue
Block a user