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.
|
- `urgent` - Matches if the window wants/doesn't want attentions.
|
||||||
- `focused` - Matches if the window is/isn't focused.
|
- `focused` - Matches if the window is/isn't focused.
|
||||||
- `fullscreen` - Matches if the window is/isn't fullscreen.
|
- `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
|
- `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.
|
true for a single frame after the window has been mapped.
|
||||||
- `tag`, `tag-regex` - Matches the XDG toplevel tag of the window.
|
- `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-instance`, `x-instance-regex` - Matches the X instance of the window.
|
||||||
- `x-role`, `x-role-regex` - Matches the X role of the window.
|
- `x-role`, `x-role-regex` - Matches the X role of the window.
|
||||||
- `workspace`, `workspace-regex` - Matches the workspace 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,
|
client::ClientMatcher,
|
||||||
input::Seat,
|
input::Seat,
|
||||||
video::Mode,
|
video::Mode,
|
||||||
window::{WindowMatcher, WindowType},
|
window::{ContentType, WindowMatcher, WindowType},
|
||||||
},
|
},
|
||||||
bincode::Options,
|
bincode::Options,
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
|
|
@ -119,6 +119,7 @@ pub enum WindowCriterionIpc {
|
||||||
Fullscreen,
|
Fullscreen,
|
||||||
JustMapped,
|
JustMapped,
|
||||||
Workspace(Workspace),
|
Workspace(Workspace),
|
||||||
|
ContentTypes(ContentType),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,10 @@ use {
|
||||||
Transform, VrrMode,
|
Transform, VrrMode,
|
||||||
connector_type::{CON_UNKNOWN, ConnectorType},
|
connector_type::{CON_UNKNOWN, ConnectorType},
|
||||||
},
|
},
|
||||||
window::{MatchedWindow, TileState, Window, WindowCriterion, WindowMatcher, WindowType},
|
window::{
|
||||||
|
ContentType, MatchedWindow, TileState, Window, WindowCriterion, WindowMatcher,
|
||||||
|
WindowType,
|
||||||
|
},
|
||||||
xwayland::XScalingMode,
|
xwayland::XScalingMode,
|
||||||
},
|
},
|
||||||
bincode::Options,
|
bincode::Options,
|
||||||
|
|
@ -413,6 +416,12 @@ impl ConfigClient {
|
||||||
kind
|
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 {
|
pub fn window_id(&self, window: Window) -> String {
|
||||||
let res = self.send_with_response(&ClientMessage::GetWindowId { window });
|
let res = self.send_with_response(&ClientMessage::GetWindowId { window });
|
||||||
get_response!(res, String::new(), GetWindowId { id });
|
get_response!(res, String::new(), GetWindowId { id });
|
||||||
|
|
@ -1682,6 +1691,7 @@ impl ConfigClient {
|
||||||
WindowCriterion::Workspace(t) => WindowCriterionIpc::Workspace(t),
|
WindowCriterion::Workspace(t) => WindowCriterionIpc::Workspace(t),
|
||||||
WindowCriterion::WorkspaceName(t) => string!(t, Workspace, false),
|
WindowCriterion::WorkspaceName(t) => string!(t, Workspace, false),
|
||||||
WindowCriterion::WorkspaceNameRegex(t) => string!(t, Workspace, true),
|
WindowCriterion::WorkspaceNameRegex(t) => string!(t, Workspace, true),
|
||||||
|
WindowCriterion::ContentTypes(t) => WindowCriterionIpc::ContentTypes(t),
|
||||||
};
|
};
|
||||||
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
|
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
|
||||||
get_response!(
|
get_response!(
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use {
|
||||||
ColorSpace, Connector, DrmDevice, Format, GfxApi, TearingMode, TransferFunction,
|
ColorSpace, Connector, DrmDevice, Format, GfxApi, TearingMode, TransferFunction,
|
||||||
Transform, VrrMode, connector_type::ConnectorType,
|
Transform, VrrMode, connector_type::ConnectorType,
|
||||||
},
|
},
|
||||||
window::{TileState, Window, WindowMatcher, WindowType},
|
window::{ContentType, TileState, Window, WindowMatcher, WindowType},
|
||||||
xwayland::XScalingMode,
|
xwayland::XScalingMode,
|
||||||
},
|
},
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
|
|
@ -718,6 +718,9 @@ pub enum ClientMessage<'a> {
|
||||||
device: InputDevice,
|
device: InputDevice,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
},
|
},
|
||||||
|
GetContentType {
|
||||||
|
window: Window,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
@ -944,6 +947,9 @@ pub enum Response {
|
||||||
CreateWindowMatcher {
|
CreateWindowMatcher {
|
||||||
matcher: WindowMatcher,
|
matcher: WindowMatcher,
|
||||||
},
|
},
|
||||||
|
GetContentType {
|
||||||
|
kind: ContentType,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[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.
|
/// The tile state of a window.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||||
|
|
@ -86,6 +101,11 @@ impl Window {
|
||||||
get!(WindowType(0)).window_type(self)
|
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.
|
/// Returns the identifier of the window.
|
||||||
///
|
///
|
||||||
/// This is the identifier used in the `ext-foreign-toplevel-list-v1` protocol.
|
/// This is the identifier used in the `ext-foreign-toplevel-list-v1` protocol.
|
||||||
|
|
@ -292,6 +312,8 @@ pub enum WindowCriterion<'a> {
|
||||||
WorkspaceName(&'a str),
|
WorkspaceName(&'a str),
|
||||||
/// Matches the workspace name of the window with a regular expression.
|
/// Matches the workspace name of the window with a regular expression.
|
||||||
WorkspaceNameRegex(&'a str),
|
WorkspaceNameRegex(&'a str),
|
||||||
|
/// Matches if the window has one of the content types.
|
||||||
|
ContentTypes(ContentType),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowCriterion<'_> {
|
impl WindowCriterion<'_> {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@ use {
|
||||||
tlm::{TlmLeafMatcher, TlmUpstreamNode},
|
tlm::{TlmLeafMatcher, TlmUpstreamNode},
|
||||||
},
|
},
|
||||||
format::config_formats,
|
format::config_formats,
|
||||||
ifs::wl_seat::{SeatId, WlSeatGlobal},
|
ifs::{
|
||||||
|
wl_seat::{SeatId, WlSeatGlobal},
|
||||||
|
wp_content_type_v1::ContentTypeExt,
|
||||||
|
},
|
||||||
io_uring::TaskResultExt,
|
io_uring::TaskResultExt,
|
||||||
kbvm::{KbvmError, KbvmMap},
|
kbvm::{KbvmError, KbvmMap},
|
||||||
output_schedule::map_cursor_hz,
|
output_schedule::map_cursor_hz,
|
||||||
|
|
@ -2062,6 +2065,7 @@ impl ConfigProxyHandler {
|
||||||
WindowCriterionIpc::Workspace(w) => mgr.workspace(CritLiteralOrRegex::Literal(
|
WindowCriterionIpc::Workspace(w) => mgr.workspace(CritLiteralOrRegex::Literal(
|
||||||
self.get_workspace(*w)?.to_string(),
|
self.get_workspace(*w)?.to_string(),
|
||||||
)),
|
)),
|
||||||
|
WindowCriterionIpc::ContentTypes(t) => mgr.content_type(*t),
|
||||||
};
|
};
|
||||||
let cached = Rc::new(CachedCriterion {
|
let cached = Rc::new(CachedCriterion {
|
||||||
crit: criterion.clone(),
|
crit: criterion.clone(),
|
||||||
|
|
@ -2356,6 +2360,17 @@ impl ConfigProxyHandler {
|
||||||
Ok(())
|
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) {
|
fn handle_window_exists(&self, window: Window) {
|
||||||
self.respond(Response::WindowExists {
|
self.respond(Response::WindowExists {
|
||||||
exists: self.get_window(window).is_ok(),
|
exists: self.get_window(window).is_ok(),
|
||||||
|
|
@ -2964,6 +2979,9 @@ impl ConfigProxyHandler {
|
||||||
ClientMessage::SetMiddleButtonEmulationEnabled { device, enabled } => self
|
ClientMessage::SetMiddleButtonEmulationEnabled { device, enabled } => self
|
||||||
.handle_set_middle_button_emulation_enabled(device, enabled)
|
.handle_set_middle_button_emulation_enabled(device, enabled)
|
||||||
.wrn("set_middle_button_emulation_enabled")?,
|
.wrn("set_middle_button_emulation_enabled")?,
|
||||||
|
ClientMessage::GetContentType { window } => self
|
||||||
|
.handle_get_content_type(window)
|
||||||
|
.wrn("get_content_type")?,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use {
|
||||||
crit_matchers::critm_constant::CritMatchConstant,
|
crit_matchers::critm_constant::CritMatchConstant,
|
||||||
tlm::tlm_matchers::{
|
tlm::tlm_matchers::{
|
||||||
tlmm_client::TlmMatchClient,
|
tlmm_client::TlmMatchClient,
|
||||||
|
tlmm_content_type::TlmMatchContentType,
|
||||||
tlmm_floating::TlmMatchFloating,
|
tlmm_floating::TlmMatchFloating,
|
||||||
tlmm_fullscreen::TlmMatchFullscreen,
|
tlmm_fullscreen::TlmMatchFullscreen,
|
||||||
tlmm_just_mapped::TlmMatchJustMapped,
|
tlmm_just_mapped::TlmMatchJustMapped,
|
||||||
|
|
@ -34,7 +35,7 @@ use {
|
||||||
toplevel_identifier::ToplevelIdentifier,
|
toplevel_identifier::ToplevelIdentifier,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
jay_config::window::WindowType,
|
jay_config::window::{ContentType, WindowType},
|
||||||
linearize::static_map,
|
linearize::static_map,
|
||||||
std::{
|
std::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
|
@ -58,6 +59,7 @@ bitflags! {
|
||||||
TL_CHANGED_CLASS_INST = 1 << 11,
|
TL_CHANGED_CLASS_INST = 1 << 11,
|
||||||
TL_CHANGED_ROLE = 1 << 12,
|
TL_CHANGED_ROLE = 1 << 12,
|
||||||
TL_CHANGED_WORKSPACE = 1 << 13,
|
TL_CHANGED_WORKSPACE = 1 << 13,
|
||||||
|
TL_CHANGED_CONTENT_TY = 1 << 14,
|
||||||
}
|
}
|
||||||
|
|
||||||
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
|
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
|
||||||
|
|
@ -90,6 +92,7 @@ pub struct RootMatchers {
|
||||||
instance: TlmRootMatcherMap<TlmMatchInstance>,
|
instance: TlmRootMatcherMap<TlmMatchInstance>,
|
||||||
role: TlmRootMatcherMap<TlmMatchRole>,
|
role: TlmRootMatcherMap<TlmMatchRole>,
|
||||||
workspace: TlmRootMatcherMap<TlmMatchWorkspace>,
|
workspace: TlmRootMatcherMap<TlmMatchWorkspace>,
|
||||||
|
content_ty: TlmRootMatcherMap<TlmMatchContentType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_tl_changes(state: Rc<State>) {
|
pub async fn handle_tl_changes(state: Rc<State>) {
|
||||||
|
|
@ -222,6 +225,7 @@ impl TlMatcherManager {
|
||||||
conditional!(TL_CHANGED_CLASS_INST, instance);
|
conditional!(TL_CHANGED_CLASS_INST, instance);
|
||||||
conditional!(TL_CHANGED_ROLE, role);
|
conditional!(TL_CHANGED_ROLE, role);
|
||||||
conditional!(TL_CHANGED_WORKSPACE, workspace);
|
conditional!(TL_CHANGED_WORKSPACE, workspace);
|
||||||
|
conditional!(TL_CHANGED_CONTENT_TY, content_ty);
|
||||||
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
||||||
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
||||||
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
||||||
|
|
@ -299,6 +303,7 @@ impl TlMatcherManager {
|
||||||
conditional!(TL_CHANGED_CLASS_INST, instance);
|
conditional!(TL_CHANGED_CLASS_INST, instance);
|
||||||
conditional!(TL_CHANGED_ROLE, role);
|
conditional!(TL_CHANGED_ROLE, role);
|
||||||
conditional!(TL_CHANGED_WORKSPACE, workspace);
|
conditional!(TL_CHANGED_WORKSPACE, workspace);
|
||||||
|
conditional!(TL_CHANGED_CONTENT_TY, content_ty);
|
||||||
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
||||||
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
||||||
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
||||||
|
|
@ -372,6 +377,10 @@ impl TlMatcherManager {
|
||||||
pub fn workspace(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
pub fn workspace(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
||||||
self.root(TlmMatchWorkspace::new(string))
|
self.root(TlmMatchWorkspace::new(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn content_type(&self, kind: ContentType) -> Rc<TlmUpstreamNode> {
|
||||||
|
self.root(TlmMatchContentType::new(kind))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CritTarget for ToplevelData {
|
impl CritTarget for ToplevelData {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ macro_rules! fixed_root_criterion {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod tlmm_client;
|
pub mod tlmm_client;
|
||||||
|
pub mod tlmm_content_type;
|
||||||
pub mod tlmm_floating;
|
pub mod tlmm_floating;
|
||||||
pub mod tlmm_fullscreen;
|
pub mod tlmm_fullscreen;
|
||||||
pub mod tlmm_just_mapped;
|
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>) {
|
fn after_apply_commit(self: Rc<Self>) {
|
||||||
if let Some(xwindow) = self.xwindow.get() {
|
if let Some(xwindow) = self.xwindow.get() {
|
||||||
xwindow.map_status_changed();
|
xwindow.map_status_changed();
|
||||||
|
xwindow
|
||||||
|
.toplevel_data
|
||||||
|
.set_content_type(self.surface.content_type.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@ impl Xwindow {
|
||||||
weak,
|
weak,
|
||||||
);
|
);
|
||||||
tld.pos.set(surface.extents.get());
|
tld.pos.set(surface.extents.get());
|
||||||
|
tld.content_type.set(surface.content_type.get());
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,17 @@ impl XdgToplevel {
|
||||||
let data = Rc::new(XdgToplevelToplevelData {
|
let data = Rc::new(XdgToplevelToplevelData {
|
||||||
tag: Default::default(),
|
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 {
|
Self {
|
||||||
id,
|
id,
|
||||||
state: state.clone(),
|
state: state.clone(),
|
||||||
|
|
@ -161,14 +172,7 @@ impl XdgToplevel {
|
||||||
max_width: Cell::new(None),
|
max_width: Cell::new(None),
|
||||||
max_height: Cell::new(None),
|
max_height: Cell::new(None),
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
toplevel_data: ToplevelData::new(
|
toplevel_data,
|
||||||
state,
|
|
||||||
String::new(),
|
|
||||||
Some(surface.surface.client.clone()),
|
|
||||||
ToplevelType::XdgToplevel(data.clone()),
|
|
||||||
node_id,
|
|
||||||
slf,
|
|
||||||
),
|
|
||||||
drag: Default::default(),
|
drag: Default::default(),
|
||||||
is_mapped: Cell::new(false),
|
is_mapped: Cell::new(false),
|
||||||
dialog: Default::default(),
|
dialog: Default::default(),
|
||||||
|
|
@ -518,6 +522,8 @@ impl XdgToplevel {
|
||||||
self.state.tree_changed();
|
self.state.tree_changed();
|
||||||
self.toplevel_data.broadcast(self.clone());
|
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},
|
object::{Object, Version},
|
||||||
wire::{WpContentTypeV1Id, wp_content_type_v1::*},
|
wire::{WpContentTypeV1Id, wp_content_type_v1::*},
|
||||||
},
|
},
|
||||||
|
jay_config::window::{
|
||||||
|
ContentType as ConfigContentType, GAME_CONTENT, NO_CONTENT_TYPE, PHOTO_CONTENT,
|
||||||
|
VIDEO_CONTENT,
|
||||||
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
@ -22,6 +26,21 @@ pub enum ContentType {
|
||||||
Game,
|
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 struct WpContentTypeV1 {
|
||||||
pub id: WpContentTypeV1Id,
|
pub id: WpContentTypeV1Id,
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ use {
|
||||||
criteria::{
|
criteria::{
|
||||||
CritDestroyListener, CritMatcherId,
|
CritDestroyListener, CritMatcherId,
|
||||||
tlm::{
|
tlm::{
|
||||||
TL_CHANGED_APP_ID, TL_CHANGED_DESTROYED, TL_CHANGED_FLOATING,
|
TL_CHANGED_APP_ID, TL_CHANGED_CONTENT_TY, TL_CHANGED_DESTROYED,
|
||||||
TL_CHANGED_FULLSCREEN, TL_CHANGED_NEW, TL_CHANGED_TITLE, TL_CHANGED_URGENT,
|
TL_CHANGED_FLOATING, TL_CHANGED_FULLSCREEN, TL_CHANGED_NEW, TL_CHANGED_TITLE,
|
||||||
TL_CHANGED_VISIBLE, TL_CHANGED_WORKSPACE, TlMatcherChange,
|
TL_CHANGED_URGENT, TL_CHANGED_VISIBLE, TL_CHANGED_WORKSPACE, TlMatcherChange,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ifs::{
|
ifs::{
|
||||||
|
|
@ -20,6 +20,7 @@ use {
|
||||||
WlSurface, x_surface::xwindow::XwindowData,
|
WlSurface, x_surface::xwindow::XwindowData,
|
||||||
xdg_surface::xdg_toplevel::XdgToplevelToplevelData,
|
xdg_surface::xdg_toplevel::XdgToplevelToplevelData,
|
||||||
},
|
},
|
||||||
|
wp_content_type_v1::ContentType,
|
||||||
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
||||||
zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1,
|
zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1,
|
||||||
},
|
},
|
||||||
|
|
@ -371,6 +372,7 @@ pub struct ToplevelData {
|
||||||
pub changed_properties: Cell<TlMatcherChange>,
|
pub changed_properties: Cell<TlMatcherChange>,
|
||||||
pub just_mapped_scheduled: Cell<bool>,
|
pub just_mapped_scheduled: Cell<bool>,
|
||||||
pub seat_foci: CopyHashMap<SeatId, ()>,
|
pub seat_foci: CopyHashMap<SeatId, ()>,
|
||||||
|
pub content_type: Cell<Option<ContentType>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelData {
|
impl ToplevelData {
|
||||||
|
|
@ -422,6 +424,7 @@ impl ToplevelData {
|
||||||
changed_properties: Default::default(),
|
changed_properties: Default::default(),
|
||||||
just_mapped_scheduled: Cell::new(false),
|
just_mapped_scheduled: Cell::new(false),
|
||||||
seat_foci: Default::default(),
|
seat_foci: Default::default(),
|
||||||
|
content_type: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -857,6 +860,12 @@ impl ToplevelData {
|
||||||
pub fn just_mapped(&self) -> bool {
|
pub fn just_mapped(&self) -> bool {
|
||||||
self.mapped_during_iteration.get() == self.state.eng.iteration()
|
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 {
|
impl Drop for ToplevelData {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ use {
|
||||||
status::MessageFormat,
|
status::MessageFormat,
|
||||||
theme::Color,
|
theme::Color,
|
||||||
video::{ColorSpace, Format, GfxApi, TearingMode, TransferFunction, Transform, VrrMode},
|
video::{ColorSpace, Format, GfxApi, TearingMode, TransferFunction, Transform, VrrMode},
|
||||||
window::{TileState, WindowType},
|
window::{ContentType, TileState, WindowType},
|
||||||
xwayland::XScalingMode,
|
xwayland::XScalingMode,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
|
|
@ -277,6 +277,7 @@ pub struct WindowMatch {
|
||||||
pub x_role_regex: Option<String>,
|
pub x_role_regex: Option<String>,
|
||||||
pub workspace: Option<String>,
|
pub workspace: Option<String>,
|
||||||
pub workspace_regex: Option<String>,
|
pub workspace_regex: Option<String>,
|
||||||
|
pub content_types: Option<ContentType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ pub mod color_management;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
mod connector;
|
mod connector;
|
||||||
mod connector_match;
|
mod connector_match;
|
||||||
|
mod content_type;
|
||||||
mod drm_device;
|
mod drm_device;
|
||||||
mod drm_device_match;
|
mod drm_device_match;
|
||||||
mod env;
|
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},
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
parsers::{
|
parsers::{
|
||||||
client_match::{ClientMatchParser, ClientMatchParserError},
|
client_match::{ClientMatchParser, ClientMatchParserError},
|
||||||
|
content_type::{ContentTypeParser, ContentTypeParserError},
|
||||||
window_type::{WindowTypeParser, WindowTypeParserError},
|
window_type::{WindowTypeParser, WindowTypeParserError},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -29,6 +30,8 @@ pub enum WindowMatchParserError {
|
||||||
WindowTypes(#[from] WindowTypeParserError),
|
WindowTypes(#[from] WindowTypeParserError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ClientMatchParserError(#[from] ClientMatchParserError),
|
ClientMatchParserError(#[from] ClientMatchParserError),
|
||||||
|
#[error(transparent)]
|
||||||
|
ContentTypes(#[from] ContentTypeParserError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WindowMatchParser<'a>(pub &'a Context<'a>);
|
pub struct WindowMatchParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
@ -77,6 +80,7 @@ impl Parser for WindowMatchParser<'_> {
|
||||||
x_role_regex,
|
x_role_regex,
|
||||||
workspace,
|
workspace,
|
||||||
workspace_regex,
|
workspace_regex,
|
||||||
|
content_types_val,
|
||||||
),
|
),
|
||||||
) = ext.extract((
|
) = ext.extract((
|
||||||
(
|
(
|
||||||
|
|
@ -111,6 +115,7 @@ impl Parser for WindowMatchParser<'_> {
|
||||||
opt(str("x-role-regex")),
|
opt(str("x-role-regex")),
|
||||||
opt(str("workspace")),
|
opt(str("workspace")),
|
||||||
opt(str("workspace-regex")),
|
opt(str("workspace-regex")),
|
||||||
|
opt(val("content-types")),
|
||||||
),
|
),
|
||||||
))?;
|
))?;
|
||||||
let mut not = None;
|
let mut not = None;
|
||||||
|
|
@ -144,6 +149,10 @@ impl Parser for WindowMatchParser<'_> {
|
||||||
if let Some(value) = client_val {
|
if let Some(value) = client_val {
|
||||||
client = Some(value.parse_map(&mut ClientMatchParser(self.0))?);
|
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 {
|
Ok(WindowMatch {
|
||||||
generic: GenericMatch {
|
generic: GenericMatch {
|
||||||
name: name.despan_into(),
|
name: name.despan_into(),
|
||||||
|
|
@ -174,6 +183,7 @@ impl Parser for WindowMatchParser<'_> {
|
||||||
workspace_regex: workspace_regex.despan_into(),
|
workspace_regex: workspace_regex.despan_into(),
|
||||||
types,
|
types,
|
||||||
client,
|
client,
|
||||||
|
content_types,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -281,6 +281,9 @@ impl Rule for WindowRule {
|
||||||
};
|
};
|
||||||
all.push(matcher);
|
all.push(matcher);
|
||||||
}
|
}
|
||||||
|
if let Some(value) = &match_.content_types {
|
||||||
|
all.push(m(WindowCriterion::ContentTypes(*value)));
|
||||||
|
}
|
||||||
Some(())
|
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": {
|
"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",
|
"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",
|
"type": "object",
|
||||||
|
|
@ -1888,6 +1912,10 @@
|
||||||
"workspace-regex": {
|
"workspace-regex": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Matches the workspace of the window with a regular expression."
|
"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": []
|
"required": []
|
||||||
|
|
|
||||||
|
|
@ -1855,6 +1855,47 @@ The table has the following fields:
|
||||||
The value of this field should be a string.
|
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>
|
<a name="types-DrmDevice"></a>
|
||||||
### `DrmDevice`
|
### `DrmDevice`
|
||||||
|
|
||||||
|
|
@ -4216,6 +4257,12 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a string.
|
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>
|
<a name="types-WindowMatchExactly"></a>
|
||||||
### `WindowMatchExactly`
|
### `WindowMatchExactly`
|
||||||
|
|
|
||||||
|
|
@ -3615,6 +3615,10 @@ WindowMatch:
|
||||||
kind: string
|
kind: string
|
||||||
required: false
|
required: false
|
||||||
description: Matches the workspace of the window with a regular expression.
|
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:
|
WindowMatchExactly:
|
||||||
|
|
@ -3668,3 +3672,27 @@ TileState:
|
||||||
description: The window is tiled.
|
description: The window is tiled.
|
||||||
- value: floating
|
- value: floating
|
||||||
description: The window is 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