Merge pull request #511 from mahkoh/jorth/content-type-window-rules
config: add content-type window criteria
This commit is contained in:
commit
35adc21ca6
22 changed files with 327 additions and 18 deletions
|
|
@ -249,7 +249,6 @@ The full specification of window criteria can be found in
|
|||
- `urgent` - Matches if the window wants/doesn't want attentions.
|
||||
- `focused` - Matches if the window is/isn't focused.
|
||||
- `fullscreen` - Matches if the window is/isn't fullscreen.
|
||||
- `just-mapped` - Matches if the window has/hasn't just been mapped. This is
|
||||
- `just-mapped` - Matches if the window has/hasn't just been mapped. This is
|
||||
true for a single frame after the window has been mapped.
|
||||
- `tag`, `tag-regex` - Matches the XDG toplevel tag of the window.
|
||||
|
|
@ -257,3 +256,5 @@ The full specification of window criteria can be found in
|
|||
- `x-instance`, `x-instance-regex` - Matches the X instance of the window.
|
||||
- `x-role`, `x-role-regex` - Matches the X role of the window.
|
||||
- `workspace`, `workspace-regex` - Matches the workspace of the window.
|
||||
- `content-types` - Matches the content type of a window. Currently there are
|
||||
three types: photos, videos, and games.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use {
|
|||
client::ClientMatcher,
|
||||
input::Seat,
|
||||
video::Mode,
|
||||
window::{WindowMatcher, WindowType},
|
||||
window::{ContentType, WindowMatcher, WindowType},
|
||||
},
|
||||
bincode::Options,
|
||||
serde::{Deserialize, Serialize},
|
||||
|
|
@ -119,6 +119,7 @@ pub enum WindowCriterionIpc {
|
|||
Fullscreen,
|
||||
JustMapped,
|
||||
Workspace(Workspace),
|
||||
ContentTypes(ContentType),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -32,7 +32,10 @@ use {
|
|||
Transform, VrrMode,
|
||||
connector_type::{CON_UNKNOWN, ConnectorType},
|
||||
},
|
||||
window::{MatchedWindow, TileState, Window, WindowCriterion, WindowMatcher, WindowType},
|
||||
window::{
|
||||
ContentType, MatchedWindow, TileState, Window, WindowCriterion, WindowMatcher,
|
||||
WindowType,
|
||||
},
|
||||
xwayland::XScalingMode,
|
||||
},
|
||||
bincode::Options,
|
||||
|
|
@ -413,6 +416,12 @@ impl ConfigClient {
|
|||
kind
|
||||
}
|
||||
|
||||
pub fn content_type(&self, window: Window) -> ContentType {
|
||||
let res = self.send_with_response(&ClientMessage::GetContentType { window });
|
||||
get_response!(res, ContentType(0), GetContentType { kind });
|
||||
kind
|
||||
}
|
||||
|
||||
pub fn window_id(&self, window: Window) -> String {
|
||||
let res = self.send_with_response(&ClientMessage::GetWindowId { window });
|
||||
get_response!(res, String::new(), GetWindowId { id });
|
||||
|
|
@ -1682,6 +1691,7 @@ impl ConfigClient {
|
|||
WindowCriterion::Workspace(t) => WindowCriterionIpc::Workspace(t),
|
||||
WindowCriterion::WorkspaceName(t) => string!(t, Workspace, false),
|
||||
WindowCriterion::WorkspaceNameRegex(t) => string!(t, Workspace, true),
|
||||
WindowCriterion::ContentTypes(t) => WindowCriterionIpc::ContentTypes(t),
|
||||
};
|
||||
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
|
||||
get_response!(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use {
|
|||
ColorSpace, Connector, DrmDevice, Format, GfxApi, TearingMode, TransferFunction,
|
||||
Transform, VrrMode, connector_type::ConnectorType,
|
||||
},
|
||||
window::{TileState, Window, WindowMatcher, WindowType},
|
||||
window::{ContentType, TileState, Window, WindowMatcher, WindowType},
|
||||
xwayland::XScalingMode,
|
||||
},
|
||||
serde::{Deserialize, Serialize},
|
||||
|
|
@ -718,6 +718,9 @@ pub enum ClientMessage<'a> {
|
|||
device: InputDevice,
|
||||
enabled: bool,
|
||||
},
|
||||
GetContentType {
|
||||
window: Window,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
@ -944,6 +947,9 @@ pub enum Response {
|
|||
CreateWindowMatcher {
|
||||
matcher: WindowMatcher,
|
||||
},
|
||||
GetContentType {
|
||||
kind: ContentType,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -41,6 +41,21 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// The content type of a window.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct ContentType(pub u64) {
|
||||
/// No content type.
|
||||
pub const NO_CONTENT_TYPE = 1 << 0,
|
||||
/// Photo content type.
|
||||
pub const PHOTO_CONTENT = 1 << 1,
|
||||
/// Video content type.
|
||||
pub const VIDEO_CONTENT = 1 << 2,
|
||||
/// Game content type.
|
||||
pub const GAME_CONTENT = 1 << 3,
|
||||
}
|
||||
}
|
||||
|
||||
/// The tile state of a window.
|
||||
#[non_exhaustive]
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
|
|
@ -86,6 +101,11 @@ impl Window {
|
|||
get!(WindowType(0)).window_type(self)
|
||||
}
|
||||
|
||||
/// Returns the content type of the window.
|
||||
pub fn content_type(self) -> ContentType {
|
||||
get!(ContentType(0)).content_type(self)
|
||||
}
|
||||
|
||||
/// Returns the identifier of the window.
|
||||
///
|
||||
/// This is the identifier used in the `ext-foreign-toplevel-list-v1` protocol.
|
||||
|
|
@ -292,6 +312,8 @@ pub enum WindowCriterion<'a> {
|
|||
WorkspaceName(&'a str),
|
||||
/// Matches the workspace name of the window with a regular expression.
|
||||
WorkspaceNameRegex(&'a str),
|
||||
/// Matches if the window has one of the content types.
|
||||
ContentTypes(ContentType),
|
||||
}
|
||||
|
||||
impl WindowCriterion<'_> {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ use {
|
|||
tlm::{TlmLeafMatcher, TlmUpstreamNode},
|
||||
},
|
||||
format::config_formats,
|
||||
ifs::wl_seat::{SeatId, WlSeatGlobal},
|
||||
ifs::{
|
||||
wl_seat::{SeatId, WlSeatGlobal},
|
||||
wp_content_type_v1::ContentTypeExt,
|
||||
},
|
||||
io_uring::TaskResultExt,
|
||||
kbvm::{KbvmError, KbvmMap},
|
||||
output_schedule::map_cursor_hz,
|
||||
|
|
@ -2062,6 +2065,7 @@ impl ConfigProxyHandler {
|
|||
WindowCriterionIpc::Workspace(w) => mgr.workspace(CritLiteralOrRegex::Literal(
|
||||
self.get_workspace(*w)?.to_string(),
|
||||
)),
|
||||
WindowCriterionIpc::ContentTypes(t) => mgr.content_type(*t),
|
||||
};
|
||||
let cached = Rc::new(CachedCriterion {
|
||||
crit: criterion.clone(),
|
||||
|
|
@ -2356,6 +2360,17 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_content_type(&self, window: Window) -> Result<(), CphError> {
|
||||
let kind = self
|
||||
.get_window(window)?
|
||||
.tl_data()
|
||||
.content_type
|
||||
.get()
|
||||
.to_config();
|
||||
self.respond(Response::GetContentType { kind });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_window_exists(&self, window: Window) {
|
||||
self.respond(Response::WindowExists {
|
||||
exists: self.get_window(window).is_ok(),
|
||||
|
|
@ -2964,6 +2979,9 @@ impl ConfigProxyHandler {
|
|||
ClientMessage::SetMiddleButtonEmulationEnabled { device, enabled } => self
|
||||
.handle_set_middle_button_emulation_enabled(device, enabled)
|
||||
.wrn("set_middle_button_emulation_enabled")?,
|
||||
ClientMessage::GetContentType { window } => self
|
||||
.handle_get_content_type(window)
|
||||
.wrn("get_content_type")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use {
|
|||
crit_matchers::critm_constant::CritMatchConstant,
|
||||
tlm::tlm_matchers::{
|
||||
tlmm_client::TlmMatchClient,
|
||||
tlmm_content_type::TlmMatchContentType,
|
||||
tlmm_floating::TlmMatchFloating,
|
||||
tlmm_fullscreen::TlmMatchFullscreen,
|
||||
tlmm_just_mapped::TlmMatchJustMapped,
|
||||
|
|
@ -34,7 +35,7 @@ use {
|
|||
toplevel_identifier::ToplevelIdentifier,
|
||||
},
|
||||
},
|
||||
jay_config::window::WindowType,
|
||||
jay_config::window::{ContentType, WindowType},
|
||||
linearize::static_map,
|
||||
std::{
|
||||
marker::PhantomData,
|
||||
|
|
@ -58,6 +59,7 @@ bitflags! {
|
|||
TL_CHANGED_CLASS_INST = 1 << 11,
|
||||
TL_CHANGED_ROLE = 1 << 12,
|
||||
TL_CHANGED_WORKSPACE = 1 << 13,
|
||||
TL_CHANGED_CONTENT_TY = 1 << 14,
|
||||
}
|
||||
|
||||
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
|
||||
|
|
@ -90,6 +92,7 @@ pub struct RootMatchers {
|
|||
instance: TlmRootMatcherMap<TlmMatchInstance>,
|
||||
role: TlmRootMatcherMap<TlmMatchRole>,
|
||||
workspace: TlmRootMatcherMap<TlmMatchWorkspace>,
|
||||
content_ty: TlmRootMatcherMap<TlmMatchContentType>,
|
||||
}
|
||||
|
||||
pub async fn handle_tl_changes(state: Rc<State>) {
|
||||
|
|
@ -222,6 +225,7 @@ impl TlMatcherManager {
|
|||
conditional!(TL_CHANGED_CLASS_INST, instance);
|
||||
conditional!(TL_CHANGED_ROLE, role);
|
||||
conditional!(TL_CHANGED_WORKSPACE, workspace);
|
||||
conditional!(TL_CHANGED_CONTENT_TY, content_ty);
|
||||
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
||||
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
||||
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
||||
|
|
@ -299,6 +303,7 @@ impl TlMatcherManager {
|
|||
conditional!(TL_CHANGED_CLASS_INST, instance);
|
||||
conditional!(TL_CHANGED_ROLE, role);
|
||||
conditional!(TL_CHANGED_WORKSPACE, workspace);
|
||||
conditional!(TL_CHANGED_CONTENT_TY, content_ty);
|
||||
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
||||
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
||||
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
||||
|
|
@ -372,6 +377,10 @@ impl TlMatcherManager {
|
|||
pub fn workspace(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchWorkspace::new(string))
|
||||
}
|
||||
|
||||
pub fn content_type(&self, kind: ContentType) -> Rc<TlmUpstreamNode> {
|
||||
self.root(TlmMatchContentType::new(kind))
|
||||
}
|
||||
}
|
||||
|
||||
impl CritTarget for ToplevelData {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ macro_rules! fixed_root_criterion {
|
|||
}
|
||||
|
||||
pub mod tlmm_client;
|
||||
pub mod tlmm_content_type;
|
||||
pub mod tlmm_floating;
|
||||
pub mod tlmm_fullscreen;
|
||||
pub mod tlmm_just_mapped;
|
||||
|
|
|
|||
32
src/criteria/tlm/tlm_matchers/tlmm_content_type.rs
Normal file
32
src/criteria/tlm/tlm_matchers/tlmm_content_type.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use {
|
||||
crate::{
|
||||
criteria::{
|
||||
crit_graph::CritRootCriterion,
|
||||
tlm::{RootMatchers, TlmRootMatcherMap},
|
||||
},
|
||||
ifs::wp_content_type_v1::ContentTypeExt,
|
||||
tree::ToplevelData,
|
||||
utils::bitflags::BitflagsExt,
|
||||
},
|
||||
jay_config::window::ContentType,
|
||||
};
|
||||
|
||||
pub struct TlmMatchContentType {
|
||||
kind: ContentType,
|
||||
}
|
||||
|
||||
impl TlmMatchContentType {
|
||||
pub fn new(kind: ContentType) -> TlmMatchContentType {
|
||||
Self { kind }
|
||||
}
|
||||
}
|
||||
|
||||
impl CritRootCriterion<ToplevelData> for TlmMatchContentType {
|
||||
fn matches(&self, data: &ToplevelData) -> bool {
|
||||
self.kind.0.contains(data.content_type.get().to_config().0)
|
||||
}
|
||||
|
||||
fn nodes(roots: &RootMatchers) -> Option<&TlmRootMatcherMap<Self>> {
|
||||
Some(&roots.content_ty)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,9 @@ impl SurfaceExt for XSurface {
|
|||
fn after_apply_commit(self: Rc<Self>) {
|
||||
if let Some(xwindow) = self.xwindow.get() {
|
||||
xwindow.map_status_changed();
|
||||
xwindow
|
||||
.toplevel_data
|
||||
.set_content_type(self.surface.content_type.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ impl Xwindow {
|
|||
weak,
|
||||
);
|
||||
tld.pos.set(surface.extents.get());
|
||||
tld.content_type.set(surface.content_type.get());
|
||||
Self {
|
||||
id,
|
||||
data: data.clone(),
|
||||
|
|
|
|||
|
|
@ -146,6 +146,17 @@ impl XdgToplevel {
|
|||
let data = Rc::new(XdgToplevelToplevelData {
|
||||
tag: Default::default(),
|
||||
});
|
||||
let toplevel_data = ToplevelData::new(
|
||||
state,
|
||||
String::new(),
|
||||
Some(surface.surface.client.clone()),
|
||||
ToplevelType::XdgToplevel(data.clone()),
|
||||
node_id,
|
||||
slf,
|
||||
);
|
||||
toplevel_data
|
||||
.content_type
|
||||
.set(surface.surface.content_type.get());
|
||||
Self {
|
||||
id,
|
||||
state: state.clone(),
|
||||
|
|
@ -161,14 +172,7 @@ impl XdgToplevel {
|
|||
max_width: Cell::new(None),
|
||||
max_height: Cell::new(None),
|
||||
tracker: Default::default(),
|
||||
toplevel_data: ToplevelData::new(
|
||||
state,
|
||||
String::new(),
|
||||
Some(surface.surface.client.clone()),
|
||||
ToplevelType::XdgToplevel(data.clone()),
|
||||
node_id,
|
||||
slf,
|
||||
),
|
||||
toplevel_data,
|
||||
drag: Default::default(),
|
||||
is_mapped: Cell::new(false),
|
||||
dialog: Default::default(),
|
||||
|
|
@ -518,6 +522,8 @@ impl XdgToplevel {
|
|||
self.state.tree_changed();
|
||||
self.toplevel_data.broadcast(self.clone());
|
||||
}
|
||||
self.toplevel_data
|
||||
.set_content_type(self.xdg.surface.content_type.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ use {
|
|||
object::{Object, Version},
|
||||
wire::{WpContentTypeV1Id, wp_content_type_v1::*},
|
||||
},
|
||||
jay_config::window::{
|
||||
ContentType as ConfigContentType, GAME_CONTENT, NO_CONTENT_TYPE, PHOTO_CONTENT,
|
||||
VIDEO_CONTENT,
|
||||
},
|
||||
std::rc::Rc,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
|
@ -22,6 +26,21 @@ pub enum ContentType {
|
|||
Game,
|
||||
}
|
||||
|
||||
pub trait ContentTypeExt {
|
||||
fn to_config(&self) -> ConfigContentType;
|
||||
}
|
||||
|
||||
impl ContentTypeExt for Option<ContentType> {
|
||||
fn to_config(&self) -> ConfigContentType {
|
||||
match self {
|
||||
None => NO_CONTENT_TYPE,
|
||||
Some(ContentType::Photo) => PHOTO_CONTENT,
|
||||
Some(ContentType::Video) => VIDEO_CONTENT,
|
||||
Some(ContentType::Game) => GAME_CONTENT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WpContentTypeV1 {
|
||||
pub id: WpContentTypeV1Id,
|
||||
pub client: Rc<Client>,
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ use {
|
|||
criteria::{
|
||||
CritDestroyListener, CritMatcherId,
|
||||
tlm::{
|
||||
TL_CHANGED_APP_ID, TL_CHANGED_DESTROYED, TL_CHANGED_FLOATING,
|
||||
TL_CHANGED_FULLSCREEN, TL_CHANGED_NEW, TL_CHANGED_TITLE, TL_CHANGED_URGENT,
|
||||
TL_CHANGED_VISIBLE, TL_CHANGED_WORKSPACE, TlMatcherChange,
|
||||
TL_CHANGED_APP_ID, TL_CHANGED_CONTENT_TY, TL_CHANGED_DESTROYED,
|
||||
TL_CHANGED_FLOATING, TL_CHANGED_FULLSCREEN, TL_CHANGED_NEW, TL_CHANGED_TITLE,
|
||||
TL_CHANGED_URGENT, TL_CHANGED_VISIBLE, TL_CHANGED_WORKSPACE, TlMatcherChange,
|
||||
},
|
||||
},
|
||||
ifs::{
|
||||
|
|
@ -20,6 +20,7 @@ use {
|
|||
WlSurface, x_surface::xwindow::XwindowData,
|
||||
xdg_surface::xdg_toplevel::XdgToplevelToplevelData,
|
||||
},
|
||||
wp_content_type_v1::ContentType,
|
||||
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
||||
zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1,
|
||||
},
|
||||
|
|
@ -371,6 +372,7 @@ pub struct ToplevelData {
|
|||
pub changed_properties: Cell<TlMatcherChange>,
|
||||
pub just_mapped_scheduled: Cell<bool>,
|
||||
pub seat_foci: CopyHashMap<SeatId, ()>,
|
||||
pub content_type: Cell<Option<ContentType>>,
|
||||
}
|
||||
|
||||
impl ToplevelData {
|
||||
|
|
@ -422,6 +424,7 @@ impl ToplevelData {
|
|||
changed_properties: Default::default(),
|
||||
just_mapped_scheduled: Cell::new(false),
|
||||
seat_foci: Default::default(),
|
||||
content_type: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -857,6 +860,12 @@ impl ToplevelData {
|
|||
pub fn just_mapped(&self) -> bool {
|
||||
self.mapped_during_iteration.get() == self.state.eng.iteration()
|
||||
}
|
||||
|
||||
pub fn set_content_type(&self, content_type: Option<ContentType>) {
|
||||
if self.content_type.replace(content_type) != content_type {
|
||||
self.property_changed(TL_CHANGED_CONTENT_TY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ToplevelData {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ use {
|
|||
status::MessageFormat,
|
||||
theme::Color,
|
||||
video::{ColorSpace, Format, GfxApi, TearingMode, TransferFunction, Transform, VrrMode},
|
||||
window::{TileState, WindowType},
|
||||
window::{ContentType, TileState, WindowType},
|
||||
xwayland::XScalingMode,
|
||||
},
|
||||
std::{
|
||||
|
|
@ -277,6 +277,7 @@ pub struct WindowMatch {
|
|||
pub x_role_regex: Option<String>,
|
||||
pub workspace: Option<String>,
|
||||
pub workspace_regex: Option<String>,
|
||||
pub content_types: Option<ContentType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ pub mod color_management;
|
|||
pub mod config;
|
||||
mod connector;
|
||||
mod connector_match;
|
||||
mod content_type;
|
||||
mod drm_device;
|
||||
mod drm_device_match;
|
||||
mod env;
|
||||
|
|
|
|||
53
toml-config/src/config/parsers/content_type.rs
Normal file
53
toml-config/src/config/parsers/content_type.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
use {
|
||||
crate::{
|
||||
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
jay_config::window::{
|
||||
ContentType, GAME_CONTENT, NO_CONTENT_TYPE, PHOTO_CONTENT, VIDEO_CONTENT,
|
||||
},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ContentTypeParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error("Unknown content type `{}`", .0)]
|
||||
UnknownContentType(String),
|
||||
}
|
||||
|
||||
pub struct ContentTypeParser;
|
||||
|
||||
impl Parser for ContentTypeParser {
|
||||
type Value = ContentType;
|
||||
type Error = ContentTypeParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
let ty = match string {
|
||||
"none" => NO_CONTENT_TYPE,
|
||||
"any" => !NO_CONTENT_TYPE,
|
||||
"photo" => PHOTO_CONTENT,
|
||||
"video" => VIDEO_CONTENT,
|
||||
"game" => GAME_CONTENT,
|
||||
_ => {
|
||||
return Err(
|
||||
ContentTypeParserError::UnknownContentType(string.to_owned()).spanned(span),
|
||||
);
|
||||
}
|
||||
};
|
||||
Ok(ty)
|
||||
}
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut ty = ContentType(0);
|
||||
for el in array {
|
||||
ty |= el.parse(&mut ContentTypeParser)?;
|
||||
}
|
||||
Ok(ty)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ use {
|
|||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
client_match::{ClientMatchParser, ClientMatchParserError},
|
||||
content_type::{ContentTypeParser, ContentTypeParserError},
|
||||
window_type::{WindowTypeParser, WindowTypeParserError},
|
||||
},
|
||||
},
|
||||
|
|
@ -29,6 +30,8 @@ pub enum WindowMatchParserError {
|
|||
WindowTypes(#[from] WindowTypeParserError),
|
||||
#[error(transparent)]
|
||||
ClientMatchParserError(#[from] ClientMatchParserError),
|
||||
#[error(transparent)]
|
||||
ContentTypes(#[from] ContentTypeParserError),
|
||||
}
|
||||
|
||||
pub struct WindowMatchParser<'a>(pub &'a Context<'a>);
|
||||
|
|
@ -77,6 +80,7 @@ impl Parser for WindowMatchParser<'_> {
|
|||
x_role_regex,
|
||||
workspace,
|
||||
workspace_regex,
|
||||
content_types_val,
|
||||
),
|
||||
) = ext.extract((
|
||||
(
|
||||
|
|
@ -111,6 +115,7 @@ impl Parser for WindowMatchParser<'_> {
|
|||
opt(str("x-role-regex")),
|
||||
opt(str("workspace")),
|
||||
opt(str("workspace-regex")),
|
||||
opt(val("content-types")),
|
||||
),
|
||||
))?;
|
||||
let mut not = None;
|
||||
|
|
@ -144,6 +149,10 @@ impl Parser for WindowMatchParser<'_> {
|
|||
if let Some(value) = client_val {
|
||||
client = Some(value.parse_map(&mut ClientMatchParser(self.0))?);
|
||||
}
|
||||
let mut content_types = None;
|
||||
if let Some(value) = content_types_val {
|
||||
content_types = Some(value.parse_map(&mut ContentTypeParser)?);
|
||||
}
|
||||
Ok(WindowMatch {
|
||||
generic: GenericMatch {
|
||||
name: name.despan_into(),
|
||||
|
|
@ -174,6 +183,7 @@ impl Parser for WindowMatchParser<'_> {
|
|||
workspace_regex: workspace_regex.despan_into(),
|
||||
types,
|
||||
client,
|
||||
content_types,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -281,6 +281,9 @@ impl Rule for WindowRule {
|
|||
};
|
||||
all.push(matcher);
|
||||
}
|
||||
if let Some(value) = &match_.content_types {
|
||||
all.push(m(WindowCriterion::ContentTypes(*value)));
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -924,6 +924,30 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"ContentTypeMask": {
|
||||
"description": "A mask of content types.\n",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "A named mask.",
|
||||
"enum": [
|
||||
"none",
|
||||
"any",
|
||||
"photo",
|
||||
"video",
|
||||
"game"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"description": "An array of masks that are OR'd.",
|
||||
"items": {
|
||||
"description": "",
|
||||
"$ref": "#/$defs/ContentTypeMask"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"DrmDevice": {
|
||||
"description": "Describes configuration to apply to a DRM device (graphics card).\n\n- Example: To disable direct scanout on a device:\n\n ```toml\n [[drm-devices]]\n match = { pci-vendor = 0x1002, pci-model = 0x73ff }\n direct-scanout = false\n ```\n",
|
||||
"type": "object",
|
||||
|
|
@ -1888,6 +1912,10 @@
|
|||
"workspace-regex": {
|
||||
"type": "string",
|
||||
"description": "Matches the workspace of the window with a regular expression."
|
||||
},
|
||||
"content-types": {
|
||||
"description": "Matches windows whose content type is contained in the mask.",
|
||||
"$ref": "#/$defs/ContentTypeMask"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
|
|
|||
|
|
@ -1855,6 +1855,47 @@ The table has the following fields:
|
|||
The value of this field should be a string.
|
||||
|
||||
|
||||
<a name="types-ContentTypeMask"></a>
|
||||
### `ContentTypeMask`
|
||||
|
||||
A mask of content types.
|
||||
|
||||
Values of this type should have one of the following forms:
|
||||
|
||||
#### A string
|
||||
|
||||
A named mask.
|
||||
|
||||
The string should have one of the following values:
|
||||
|
||||
- `none`:
|
||||
|
||||
The mask matching windows without a content type.
|
||||
|
||||
- `any`:
|
||||
|
||||
The mask containing every possible type except `none`.
|
||||
|
||||
- `photo`:
|
||||
|
||||
The mask matching photo content.
|
||||
|
||||
- `video`:
|
||||
|
||||
The mask matching video content.
|
||||
|
||||
- `game`:
|
||||
|
||||
The mask matching game content.
|
||||
|
||||
|
||||
#### An array
|
||||
|
||||
An array of masks that are OR'd.
|
||||
|
||||
Each element of this array should be a [ContentTypeMask](#types-ContentTypeMask).
|
||||
|
||||
|
||||
<a name="types-DrmDevice"></a>
|
||||
### `DrmDevice`
|
||||
|
||||
|
|
@ -4216,6 +4257,12 @@ The table has the following fields:
|
|||
|
||||
The value of this field should be a string.
|
||||
|
||||
- `content-types` (optional):
|
||||
|
||||
Matches windows whose content type is contained in the mask.
|
||||
|
||||
The value of this field should be a [ContentTypeMask](#types-ContentTypeMask).
|
||||
|
||||
|
||||
<a name="types-WindowMatchExactly"></a>
|
||||
### `WindowMatchExactly`
|
||||
|
|
|
|||
|
|
@ -3615,6 +3615,10 @@ WindowMatch:
|
|||
kind: string
|
||||
required: false
|
||||
description: Matches the workspace of the window with a regular expression.
|
||||
content-types:
|
||||
ref: ContentTypeMask
|
||||
required: false
|
||||
description: Matches windows whose content type is contained in the mask.
|
||||
|
||||
|
||||
WindowMatchExactly:
|
||||
|
|
@ -3668,3 +3672,27 @@ TileState:
|
|||
description: The window is tiled.
|
||||
- value: floating
|
||||
description: The window is floating.
|
||||
|
||||
|
||||
ContentTypeMask:
|
||||
description: |
|
||||
A mask of content types.
|
||||
kind: variable
|
||||
variants:
|
||||
- kind: string
|
||||
description: A named mask.
|
||||
values:
|
||||
- value: none
|
||||
description: The mask matching windows without a content type.
|
||||
- value: any
|
||||
description: The mask containing every possible type except `none`.
|
||||
- value: photo
|
||||
description: The mask matching photo content.
|
||||
- value: video
|
||||
description: The mask matching video content.
|
||||
- value: game
|
||||
description: The mask matching game content.
|
||||
- kind: array
|
||||
description: An array of masks that are OR'd.
|
||||
items:
|
||||
ref: ContentTypeMask
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue