1
0
Fork 0
forked from wry/wry

autocommit 2022-04-10 18:26:13 CEST

This commit is contained in:
Julian Orth 2022-04-10 18:26:13 +02:00
parent af152f7f3e
commit 6b3316e920
26 changed files with 514 additions and 82 deletions

35
Cargo.lock generated
View file

@ -123,6 +123,19 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi",
]
[[package]]
name = "clap"
version = "3.1.6"
@ -167,6 +180,7 @@ dependencies = [
name = "default-config"
version = "0.1.0"
dependencies = [
"chrono",
"jay-config",
"log",
"rand",
@ -387,6 +401,16 @@ dependencies = [
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@ -663,6 +687,17 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "uapi"
version = "0.2.7"

View file

@ -10,3 +10,4 @@ crate-type = ["lib", "cdylib"]
jay-config = { path = "../jay-config" }
log = "0.4.14"
rand = "0.8.5"
chrono = "0.4.19"

View file

@ -1,25 +1,31 @@
use jay_config::{
config,
drm::{get_connector, on_connector_connected, on_new_connector},
embedded::grab_input_device,
get_workspace,
input::{
capability::{CAP_KEYBOARD, CAP_POINTER},
create_seat, input_devices, on_new_input_device, InputDevice, Seat,
},
keyboard::{
mods::{Modifiers, ALT, CTRL, SHIFT},
syms::{
SYM_Super_L, SYM_b, SYM_c, SYM_d, SYM_f, SYM_h, SYM_j, SYM_k, SYM_l, SYM_m, SYM_p,
SYM_q, SYM_t, SYM_v, SYM_y, SYM_F1, SYM_F10, SYM_F11, SYM_F12, SYM_F13, SYM_F14,
SYM_F15, SYM_F16, SYM_F17, SYM_F18, SYM_F19, SYM_F2, SYM_F20, SYM_F21, SYM_F22,
SYM_F23, SYM_F24, SYM_F25, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9,
use {
chrono::{format::StrftimeItems, Local, Timelike},
jay_config::{
config,
drm::{get_connector, on_connector_connected, on_new_connector},
embedded::grab_input_device,
get_timer, get_workspace,
input::{
capability::{CAP_KEYBOARD, CAP_POINTER},
create_seat, input_devices, on_new_input_device, InputDevice, Seat,
},
keyboard::{
mods::{Modifiers, ALT, CTRL, SHIFT},
syms::{
SYM_Super_L, SYM_b, SYM_c, SYM_d, SYM_f, SYM_h, SYM_j, SYM_k, SYM_l, SYM_m, SYM_p,
SYM_q, SYM_t, SYM_v, SYM_y, SYM_F1, SYM_F10, SYM_F11, SYM_F12, SYM_F13, SYM_F14,
SYM_F15, SYM_F16, SYM_F17, SYM_F18, SYM_F19, SYM_F2, SYM_F20, SYM_F21, SYM_F22,
SYM_F23, SYM_F24, SYM_F25, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9,
},
},
quit,
status::set_status,
switch_to_vt,
Axis::{Horizontal, Vertical},
Command,
Direction::{Down, Left, Right, Up},
},
quit, switch_to_vt,
Axis::{Horizontal, Vertical},
Command,
Direction::{Down, Left, Right, Up},
std::time::Duration,
};
const MOD: Modifiers = ALT;
@ -117,6 +123,28 @@ pub fn configure() {
on_new_connector(move |_| handle_connectors_changed());
on_connector_connected(move |_| handle_connectors_changed());
handle_connectors_changed();
{
let time_format: Vec<_> = StrftimeItems::new("%Y-%m-%d %H:%M:%S").collect();
let update_status = move || {
let status = format!(
"{}",
Local::now().format_with_items(time_format.iter()),
);
set_status(&status);
};
update_status();
let initial = {
let now = Local::now();
5000 - (now.second() * 1000 + now.timestamp_subsec_millis()) % 5000
};
let timer = get_timer("status_timer");
timer.program(
Duration::from_millis(initial as u64),
Some(Duration::from_secs(5)),
);
timer.on_tick(update_status);
}
}
config!(configure);

View file

@ -14,7 +14,7 @@ use {
input::{acceleration::AccelProfile, capability::Capability, InputDevice, Seat},
keyboard::keymap::Keymap,
theme::Color,
Axis, Command, Direction, LogLevel, ModifiedKeySym, Workspace,
Axis, Command, Direction, LogLevel, ModifiedKeySym, Timer, Workspace,
},
std::{
cell::{Cell, RefCell},
@ -23,6 +23,7 @@ use {
ptr,
rc::Rc,
slice,
time::Duration,
},
};
@ -32,6 +33,7 @@ pub(crate) struct Client {
srv_unref: unsafe extern "C" fn(data: *const u8),
srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize),
key_handlers: RefCell<HashMap<(Seat, ModifiedKeySym), Rc<dyn Fn()>>>,
timer_handlers: RefCell<HashMap<Timer, Rc<dyn Fn()>>>,
response: RefCell<Vec<Response>>,
on_new_seat: RefCell<Option<Rc<dyn Fn(Seat)>>>,
on_new_input_device: RefCell<Option<Rc<dyn Fn(InputDevice)>>>,
@ -113,6 +115,7 @@ pub unsafe extern "C" fn init(
srv_unref,
srv_handler,
key_handlers: Default::default(),
timer_handlers: Default::default(),
response: Default::default(),
on_new_seat: Default::default(),
on_new_input_device: Default::default(),
@ -222,6 +225,33 @@ impl Client {
mono
}
pub fn get_timer(&self, name: &str) -> Timer {
let res = self.with_response(|| self.send(&ClientMessage::GetTimer { name }));
get_response!(res, Timer(0), GetTimer, timer);
timer
}
pub fn remove_timer(&self, timer: Timer) {
self.send(&ClientMessage::RemoveTimer { timer });
}
pub fn program_timer(
&self,
timer: Timer,
initial: Option<Duration>,
periodic: Option<Duration>,
) {
self.send(&ClientMessage::ProgramTimer {
timer,
initial,
periodic,
});
}
pub fn on_timer_tick<F: Fn() + 'static>(&self, timer: Timer, f: F) {
self.timer_handlers.borrow_mut().insert(timer, Rc::new(f));
}
pub fn get_workspace(&self, name: &str) -> Workspace {
let res = self.with_response(|| self.send(&ClientMessage::GetWorkspace { name }));
get_response!(res, Workspace(0), GetWorkspace, workspace);
@ -288,6 +318,10 @@ impl Client {
self.send(&ClientMessage::SetMono { seat, mono });
}
pub fn set_status(&self, status: &str) {
self.send(&ClientMessage::SetStatus { status });
}
pub fn set_split(&self, seat: Seat, axis: Axis) {
self.send(&ClientMessage::SetSplit { seat, axis });
}
@ -507,6 +541,12 @@ impl Client {
}
}
ServerMessage::DelConnector { .. } => {}
ServerMessage::TimerExpired { timer } => {
let handler = self.timer_handlers.borrow_mut().get(&timer).cloned();
if let Some(handler) = handler {
handler();
}
}
}
}

View file

@ -4,9 +4,10 @@ use {
input::{acceleration::AccelProfile, capability::Capability, InputDevice, Seat},
keyboard::{keymap::Keymap, mods::Modifiers, syms::KeySym},
theme::Color,
Axis, Direction, LogLevel, Workspace,
Axis, Direction, LogLevel, Timer, Workspace,
},
bincode::{BorrowDecode, Decode, Encode},
std::time::Duration,
};
#[derive(Encode, BorrowDecode, Debug)]
@ -38,6 +39,9 @@ pub enum ServerMessage {
mods: Modifiers,
sym: KeySym,
},
TimerExpired {
timer: Timer,
},
}
#[derive(Encode, BorrowDecode, Debug)]
@ -77,6 +81,9 @@ pub enum ClientMessage<'a> {
GetSplit {
seat: Seat,
},
SetStatus {
status: &'a str,
},
SetSplit {
seat: Seat,
axis: Axis,
@ -203,6 +210,17 @@ pub enum ClientMessage<'a> {
seat: Seat,
workspace: Workspace,
},
GetTimer {
name: &'a str,
},
RemoveTimer {
timer: Timer,
},
ProgramTimer {
timer: Timer,
initial: Option<Duration>,
periodic: Option<Duration>,
},
}
#[derive(Encode, Decode, Debug)]
@ -242,6 +260,9 @@ pub enum Response {
GetDeviceName {
name: String,
},
GetTimer {
timer: Timer,
},
GetWorkspace {
workspace: Workspace,
},

View file

@ -1,7 +1,7 @@
use {
crate::keyboard::{keymap::Keymap, ModifiedKeySym},
bincode::{Decode, Encode},
std::collections::HashMap,
std::{collections::HashMap, time::Duration},
};
#[macro_use]
@ -12,6 +12,7 @@ pub mod drm;
pub mod embedded;
pub mod input;
pub mod keyboard;
pub mod status;
pub mod theme;
#[derive(Encode, Decode, Copy, Clone, Debug)]
@ -91,3 +92,28 @@ pub struct Workspace(pub u64);
pub fn get_workspace(name: &str) -> Workspace {
get!(Workspace(0)).get_workspace(name)
}
#[derive(Encode, Decode, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Timer(pub u64);
pub fn get_timer(name: &str) -> Timer {
get!(Timer(0)).get_timer(name)
}
impl Timer {
pub fn program(self, initial: Duration, periodic: Option<Duration>) {
get!().program_timer(self, Some(initial), periodic);
}
pub fn cancel(self) {
get!().program_timer(self, None, None);
}
pub fn remove(self) {
get!().remove_timer(self);
}
pub fn on_tick<F: Fn() + 'static>(self, f: F) {
get!().on_timer_tick(self, f);
}
}

3
jay-config/src/status.rs Normal file
View file

@ -0,0 +1,3 @@
pub fn set_status(status: &str) {
get!().set_status(status);
}

View file

@ -3,11 +3,12 @@ pub use {
fd::{AsyncFd, FdStatus},
task::SpawnedFuture,
timeout::Timeout,
timer::Timer,
};
use {
crate::{
event_loop::{EventLoop, EventLoopError},
utils::{copyhashmap::CopyHashMap, numcell::NumCell},
utils::{copyhashmap::CopyHashMap, numcell::NumCell, oserror::OsError},
wheel::{Wheel, WheelError},
},
fd::AsyncFdData,
@ -19,15 +20,21 @@ use {
},
thiserror::Error,
timeout::TimeoutData,
uapi::OwnedFd,
uapi::{c, OwnedFd},
};
#[derive(Debug, Error)]
pub enum AsyncError {
#[error("The timer wheel returned an error: {0}")]
#[error("The timer wheel returned an error")]
WheelError(#[from] WheelError),
#[error("The event loop caused an error: {0}")]
#[error("The event loop caused an error")]
EventLoopError(#[from] EventLoopError),
#[error("Could not read from a timer")]
TimerReadError(#[source] OsError),
#[error("Could not set a timer")]
SetTimer(#[source] OsError),
#[error("Could not create a timer")]
CreateTimer(#[source] OsError),
}
#[derive(Copy, Clone, Eq, PartialEq)]
@ -71,6 +78,10 @@ impl AsyncEngine {
})
}
pub fn timer(self: &Rc<Self>, clock_id: c::c_int) -> Result<Timer, AsyncError> {
Timer::new(self, clock_id)
}
pub fn spawn<T, F: Future<Output = T> + 'static>(&self, f: F) -> SpawnedFuture<T> {
self.queue.spawn(Phase::EventHandling, f)
}
@ -146,6 +157,59 @@ mod yield_ {
}
}
mod timer {
use {
crate::async_engine::{AsyncEngine, AsyncError, AsyncFd},
std::{rc::Rc, time::Duration},
uapi::c,
};
#[derive(Clone)]
pub struct Timer {
fd: AsyncFd,
}
impl Timer {
pub(super) fn new(eng: &Rc<AsyncEngine>, clock_id: c::c_int) -> Result<Self, AsyncError> {
let fd = match uapi::timerfd_create(clock_id, c::TFD_CLOEXEC | c::TFD_NONBLOCK) {
Ok(fd) => fd,
Err(e) => return Err(AsyncError::CreateTimer(e.into())),
};
let afd = eng.fd(&Rc::new(fd))?;
Ok(Self { fd: afd })
}
pub async fn expired(&self) -> Result<u64, AsyncError> {
self.fd.readable().await?;
let mut buf = 0u64;
if let Err(e) = uapi::read(self.fd.raw(), &mut buf) {
return Err(AsyncError::TimerReadError(e.into()));
}
Ok(buf)
}
pub fn program(
&self,
initial: Option<Duration>,
periodic: Option<Duration>,
) -> Result<(), AsyncError> {
let mut timerspec: c::itimerspec = uapi::pod_zeroed();
if let Some(init) = initial {
timerspec.it_value.tv_sec = init.as_secs() as _;
timerspec.it_value.tv_nsec = init.subsec_nanos() as _;
if let Some(per) = periodic {
timerspec.it_interval.tv_sec = per.as_secs() as _;
timerspec.it_interval.tv_nsec = per.subsec_nanos() as _;
}
}
if let Err(e) = uapi::timerfd_settime(self.fd.raw(), 0, &timerspec) {
return Err(AsyncError::SetTimer(e.into()));
}
Ok(())
}
}
}
mod timeout {
use {
crate::wheel::{Wheel, WheelDispatcher, WheelId},

View file

@ -3,6 +3,7 @@ use {
async_engine::FdStatus,
backend::{AxisSource, InputEvent, KeyState, ScrollAxis},
backends::metal::MetalBackend,
ifs::wl_seat::PX_PER_SCROLL,
libinput::{
consts::{
LIBINPUT_BUTTON_STATE_PRESSED, LIBINPUT_KEY_STATE_PRESSED,
@ -14,7 +15,6 @@ use {
},
std::rc::Rc,
};
use crate::ifs::wl_seat::PX_PER_SCROLL;
macro_rules! unpack {
($slf:expr, $ev:expr) => {{

View file

@ -8,6 +8,7 @@ use {
},
fixed::Fixed,
format::XRGB8888,
ifs::wl_seat::PX_PER_SCROLL,
render::{Framebuffer, RenderContext, RenderError},
state::State,
utils::{
@ -54,7 +55,6 @@ use {
},
thiserror::Error,
};
use crate::ifs::wl_seat::PX_PER_SCROLL;
#[derive(Debug, Error)]
pub enum XBackendError {

View file

@ -119,6 +119,7 @@ fn main_(forker: Rc<ForkerProxy>, logger: Arc<Logger>, _args: &RunArgs) -> Resul
logger,
connectors: Default::default(),
outputs: Default::default(),
status: Default::default(),
});
{
let dummy_output = Rc::new(OutputNode {
@ -150,6 +151,7 @@ fn main_(forker: Rc<ForkerProxy>, logger: Arc<Logger>, _args: &RunArgs) -> Resul
render_data: Default::default(),
state: state.clone(),
is_dummy: true,
status: Default::default(),
});
let dummy_workspace = Rc::new(WorkspaceNode {
id: state.node_ids.next(),

View file

@ -19,7 +19,7 @@ use {
keyboard::ModifiedKeySym,
},
libloading::Library,
std::{cell::Cell, ptr, rc::Rc},
std::{cell::Cell, mem, ptr, rc::Rc},
thiserror::Error,
};
@ -84,7 +84,7 @@ impl ConfigProxy {
impl Drop for ConfigProxy {
fn drop(&mut self) {
unsafe {
self.handler.dropped.set(true);
self.handler.do_drop();
(self.handler.unref)(self.handler.client_data.get());
}
}
@ -120,6 +120,9 @@ impl ConfigProxy {
workspace_ids: NumCell::new(1),
workspaces_by_name: Default::default(),
workspaces_by_id: Default::default(),
timer_ids: NumCell::new(1),
timers_by_name: Default::default(),
timers_by_id: Default::default(),
});
let init_msg =
bincode::encode_to_vec(&InitMessage::V1(V1InitMessage {}), bincode_ops()).unwrap();
@ -172,6 +175,8 @@ unsafe extern "C" fn handle_msg(data: *const u8, msg: *const u8, size: usize) {
if server.dropped.get() {
return;
}
let rc = Rc::from_raw(server);
let msg = std::slice::from_raw_parts(msg, size);
server.handle_request(msg);
rc.handle_request(msg);
mem::forget(rc);
}

View file

@ -1,7 +1,9 @@
use {
crate::{
backend,
backend::{ConnectorId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId},
async_engine::{AsyncError, SpawnedFuture, Timer},
backend::{
self, ConnectorId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId,
},
compositor::MAX_EXTENTS,
ifs::wl_seat::{SeatId, WlSeatGlobal},
state::{ConnectorData, DeviceHandlerData, OutputData, State},
@ -32,8 +34,9 @@ use {
},
libloading::Library,
log::Level,
std::{cell::Cell, rc::Rc},
std::{cell::Cell, rc::Rc, time::Duration},
thiserror::Error,
uapi::c,
};
pub(super) struct ConfigProxyHandler {
@ -47,12 +50,31 @@ pub(super) struct ConfigProxyHandler {
pub next_id: NumCell<u64>,
pub keymaps: CopyHashMap<Keymap, Rc<XkbKeymap>>,
pub bufs: Stack<Vec<u8>>,
pub workspace_ids: NumCell<u64>,
pub workspaces_by_name: CopyHashMap<Rc<String>, u64>,
pub workspaces_by_id: CopyHashMap<u64, Rc<String>>,
pub timer_ids: NumCell<u64>,
pub timers_by_name: CopyHashMap<Rc<String>, Rc<TimerData>>,
pub timers_by_id: CopyHashMap<u64, Rc<TimerData>>,
}
pub(super) struct TimerData {
timer: Timer,
id: u64,
name: Rc<String>,
_handler: SpawnedFuture<()>,
}
impl ConfigProxyHandler {
pub fn do_drop(&self) {
self.dropped.set(true);
self.timers_by_name.clear();
self.timers_by_id.clear();
}
pub fn send(&self, msg: &ServerMessage) {
let mut buf = self.bufs.pop().unwrap_or_default();
buf.clear();
@ -132,6 +154,79 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_set_status(&self, status: &str) {
self.state.set_status(status);
}
fn get_timer(&self, timer: jay_config::Timer) -> Result<Rc<TimerData>, CphError> {
match self.timers_by_id.get(&timer.0) {
Some(t) => Ok(t),
_ => Err(CphError::TimerDoesNotExist(timer)),
}
}
fn handle_remove_timer(&self, timer: jay_config::Timer) -> Result<(), CphError> {
let timer = self.get_timer(timer)?;
self.timers_by_id.remove(&timer.id);
self.timers_by_name.remove(&timer.name);
Ok(())
}
fn handle_program_timer(
&self,
timer: jay_config::Timer,
initial: Option<Duration>,
periodic: Option<Duration>,
) -> Result<(), CphError> {
let timer = self.get_timer(timer)?;
timer.timer.program(initial, periodic)?;
Ok(())
}
fn handle_get_timer(self: &Rc<Self>, name: &str) -> Result<(), CphError> {
let name = Rc::new(name.to_owned());
if let Some(t) = self.timers_by_name.get(&name) {
self.respond(Response::GetTimer {
timer: jay_config::Timer(t.id),
});
return Ok(());
}
let id = self.timer_ids.fetch_add(1);
let timer = self.state.eng.timer(c::CLOCK_BOOTTIME)?;
let handler = {
let timer = timer.clone();
let slf = self.clone();
self.state.eng.spawn(async move {
loop {
match timer.expired().await {
Ok(_) => slf.send(&ServerMessage::TimerExpired {
timer: jay_config::Timer(id),
}),
Err(e) => {
log::error!("Could not wait for timer expiration: {}", ErrorFmt(e));
if let Some(timer) = slf.timers_by_id.remove(&id) {
slf.timers_by_name.remove(&timer.name);
}
return;
}
}
}
})
};
let td = Rc::new(TimerData {
timer,
id,
name: name.clone(),
_handler: handler,
});
self.timers_by_name.set(name.clone(), td.clone());
self.timers_by_id.set(id, td.clone());
self.respond(Response::GetTimer {
timer: jay_config::Timer(id),
});
Ok(())
}
fn handle_close(&self, seat: Seat) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
seat.close();
@ -361,6 +456,7 @@ impl ConfigProxyHandler {
return Err(CphError::InvalidConnectorPosition(x, y));
}
let old_pos = connector.node.global.pos.get();
connector.node.set_position(x, y);
let seats = self.state.globals.seats.lock();
for seat in seats.values() {
if seat.get_output().id == connector.node.id {
@ -371,7 +467,6 @@ impl ConfigProxyHandler {
);
}
}
connector.node.set_position(x, y);
Ok(())
}
@ -642,13 +737,13 @@ impl ConfigProxyHandler {
self.state.theme.underline_color.set(color.into());
}
pub fn handle_request(&self, msg: &[u8]) {
pub fn handle_request(self: &Rc<Self>, msg: &[u8]) {
if let Err(e) = self.handle_request_(msg) {
log::error!("Could not handle client request: {}", ErrorFmt(e));
}
}
fn handle_request_(&self, msg: &[u8]) -> Result<(), CphError> {
fn handle_request_(self: &Rc<Self>, msg: &[u8]) -> Result<(), CphError> {
let (request, _) = match bincode::decode_from_slice::<ClientMessage, _>(msg, bincode_ops())
{
Ok(msg) => msg,
@ -770,6 +865,18 @@ impl ConfigProxyHandler {
.handle_connector_set_position(connector, x, y)
.wrn("connector_set_position")?,
ClientMessage::Close { seat } => self.handle_close(seat).wrn("close")?,
ClientMessage::SetStatus { status } => self.handle_set_status(status),
ClientMessage::GetTimer { name } => self.handle_get_timer(name).wrn("get_timer")?,
ClientMessage::RemoveTimer { timer } => {
self.handle_remove_timer(timer).wrn("remove_timer")?
}
ClientMessage::ProgramTimer {
timer,
initial,
periodic,
} => self
.handle_program_timer(timer, initial, periodic)
.wrn("program_timer")?,
}
Ok(())
}
@ -801,6 +908,8 @@ enum CphError {
DeviceDoesNotExist(InputDevice),
#[error("Connector {0:?} does not exist")]
ConnectorDoesNotExist(Connector),
#[error("Timer {0:?} does not exist")]
TimerDoesNotExist(jay_config::Timer),
#[error("Connector {0:?} does not exist or is not connected")]
OutputDoesNotExist(Connector),
#[error("{0}x{1} is not a valid connector position")]
@ -817,6 +926,8 @@ enum CphError {
ParsingFailed(#[source] DecodeError),
#[error("Could not process a `{0}` request")]
FailedRequest(&'static str, #[source] Box<Self>),
#[error(transparent)]
AsyncError(#[from] AsyncError),
}
trait WithRequestName {

View file

@ -217,6 +217,16 @@ impl WlSeatGlobal {
pub fn set_position(&self, x: i32, y: i32) {
self.pos.set((Fixed::from_int(x), Fixed::from_int(y)));
self.trigger_tree_changed();
'set_output: {
let outputs = self.state.outputs.lock();
for output in outputs.values() {
if output.node.global.pos.get().contains(x, y) {
self.output.set(output.node.clone());
break 'set_output;
}
}
self.output.set(self.state.dummy_output.get().unwrap());
}
}
pub fn position(&self) -> (Fixed, Fixed) {

View file

@ -21,7 +21,7 @@ use {
wl_surface::{xdg_surface::xdg_popup::XdgPopup, WlSurface},
},
object::ObjectId,
tree::{FloatNode, Node, OutputNode, ToplevelNode},
tree::{FloatNode, Node, ToplevelNode},
utils::{clonecell::CloneCell, smallmap::SmallMap},
wire::WlDataOfferId,
xkbcommon::{ModifierState, XKB_KEY_DOWN, XKB_KEY_UP},
@ -172,6 +172,7 @@ impl WlSeatGlobal {
Some(o) => o,
_ => return,
};
self.output.set(output.node.clone());
let pos = output.node.global.pos.get();
x += Fixed::from_int(pos.x1());
y += Fixed::from_int(pos.y1());
@ -191,6 +192,7 @@ impl WlSeatGlobal {
let outputs = self.state.outputs.lock();
for output in outputs.values() {
if output.node.global.pos.get().contains(x_int, y_int) {
self.output.set(output.node.clone());
break 'warp;
}
}
@ -517,10 +519,6 @@ impl WlSeatGlobal {
// self.focus_xdg_surface(&n.xdg);
}
pub fn enter_output(self: &Rc<Self>, output: &Rc<OutputNode>) {
self.output.set(output.clone());
}
pub fn enter_surface(&self, n: &WlSurface, x: Fixed, y: Fixed) {
let serial = self.serial.fetch_add(1);
self.surface_pointer_event(0, n, |p| p.send_enter(serial, n.id, x, y));

View file

@ -293,6 +293,7 @@ impl Node for XdgPopup {
}
fn set_visible(&self, visible: bool) {
log::info!("set visible = {}", visible);
self.xdg.set_visible(visible);
self.xdg.seat_state.set_visible(self, visible);
}
@ -362,11 +363,18 @@ impl XdgSurfaceExt for XdgPopup {
*wl = Some(ws.stacked.add_last(self.clone()));
*dl = Some(state.root.stacked.add_last(self.clone()));
state.tree_changed();
self.set_visible(
self.parent
.get()
.map(|p| p.surface.visible.get())
.unwrap_or(false),
);
}
} else {
if wl.take().is_some() {
drop(wl);
drop(dl);
self.set_visible(false);
self.destroy_node(true);
self.send_popup_done();
}

View file

@ -280,16 +280,7 @@ impl Xwindow {
self.data.state.tree_changed();
}
Change::Map if self.data.info.wants_floating.get() => {
let ws = match self.data.state.root.outputs.lock().values().cloned().next() {
Some(output) => output.ensure_workspace(),
_ => self
.data
.state
.dummy_output
.get()
.unwrap()
.ensure_workspace(),
};
let ws = self.data.state.float_map_ws();
let ext = self.data.info.extents.get();
self.data
.state

View file

@ -72,6 +72,7 @@ extern "C" {
desc: *const PangoFontDescription_,
);
fn pango_layout_set_text(layout: *mut PangoLayout_, text: *const c::c_char, length: c::c_int);
fn pango_layout_set_markup(layout: *mut PangoLayout_, text: *const c::c_char, length: c::c_int);
fn pango_layout_get_pixel_size(
layout: *mut PangoLayout_,
width: *mut c::c_int,
@ -295,6 +296,12 @@ impl PangoLayout {
}
}
pub fn set_markup(&self, text: &str) {
unsafe {
pango_layout_set_markup(self.l, text.as_ptr() as _, text.len() as _);
}
}
pub fn pixel_size(&self) -> (i32, i32) {
unsafe {
let mut w = 0;

View file

@ -65,6 +65,9 @@ impl Renderer<'_> {
for title in &rd.titles {
self.render_texture(&title.tex, x + title.x, y + title.y, ARGB8888);
}
if let Some(status) = &rd.status {
self.render_texture(&status.tex, x + status.x, y + status.y, ARGB8888);
}
}
if let Some(ws) = output.workspace.get() {
self.render_workspace(&ws, x, y + th);

View file

@ -75,6 +75,7 @@ pub struct State {
pub logger: Arc<Logger>,
pub connectors: CopyHashMap<ConnectorId, Rc<ConnectorData>>,
pub outputs: CopyHashMap<ConnectorId, Rc<OutputData>>,
pub status: CloneCell<Rc<String>>,
}
pub struct InputDeviceData {
@ -265,4 +266,26 @@ impl State {
seat.workspace_changed(&output);
}
}
pub fn float_map_ws(&self) -> Rc<WorkspaceNode> {
if let Some(seat) = self.seat_queue.last() {
let output = seat.get_output();
if !output.is_dummy {
return output.ensure_workspace();
}
}
if let Some(output) = self.root.outputs.lock().values().cloned().next() {
return output.ensure_workspace();
}
self.dummy_output.get().unwrap().ensure_workspace()
}
pub fn set_status(&self, status: &str) {
let status = Rc::new(status.to_owned());
self.status.set(status.clone());
let outputs = self.root.outputs.lock();
for output in outputs.values() {
output.set_status(&status);
}
}
}

View file

@ -97,9 +97,11 @@ impl ConnectorHandler {
active_workspace: Rect::new_empty(0, 0),
inactive_workspaces: Default::default(),
titles: Default::default(),
status: None,
}),
state: self.state.clone(),
is_dummy: false,
status: self.state.status.clone(),
});
let mode = info.initial_mode;
let output_data = Rc::new(OutputData {

View file

@ -68,9 +68,13 @@ fn create_data(font: &str, width: i32, height: i32) -> Result<Data, TextError> {
})
}
pub fn measure(font: &str, text: &str) -> Result<Rect, TextError> {
pub fn measure(font: &str, text: &str, markup: bool) -> Result<Rect, TextError> {
let data = create_data(font, 1, 1)?;
data.layout.set_text(text);
if markup {
data.layout.set_markup(text);
} else {
data.layout.set_text(text);
}
Ok(data.layout.inc_pixel_rect())
}
@ -82,7 +86,7 @@ pub fn render(
text: &str,
color: Color,
) -> Result<Rc<Texture>, TextError> {
render2(ctx, 1, width, height, 1, font, text, color, true)
render2(ctx, 1, width, height, 1, font, text, color, true, false)
}
pub fn render2(
@ -95,6 +99,7 @@ pub fn render2(
text: &str,
color: Color,
ellipsize: bool,
markup: bool,
) -> Result<Rc<Texture>, TextError> {
let data = create_data(font, width, height)?;
if ellipsize {
@ -102,7 +107,11 @@ pub fn render2(
.set_width((width - 2 * padding).max(0) * PANGO_SCALE);
data.layout.set_ellipsize(PANGO_ELLIPSIZE_END);
}
data.layout.set_text(text);
if markup {
data.layout.set_markup(text);
} else {
data.layout.set_text(text);
}
let font_height = data.layout.pixel_size().1;
data.cctx.set_operator(CAIRO_OPERATOR_SOURCE);
data.cctx
@ -127,8 +136,9 @@ pub fn render_fitting(
font: &str,
text: &str,
color: Color,
markup: bool,
) -> Result<Rc<Texture>, TextError> {
let rect = measure(font, text)?;
let rect = measure(font, text, markup)?;
render2(
ctx,
rect.x1().neg(),
@ -139,5 +149,6 @@ pub fn render_fitting(
text,
color,
false,
markup,
)
}

View file

@ -3,7 +3,10 @@ use {
backend::KeyState,
cursor::KnownCursor,
fixed::Fixed,
ifs::wl_seat::{NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT},
ifs::wl_seat::{
wl_pointer::{PendingScroll, VERTICAL_SCROLL},
NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT, PX_PER_SCROLL,
},
rect::Rect,
render::{Renderer, Texture},
state::State,
@ -32,8 +35,6 @@ use {
rc::Rc,
},
};
use crate::ifs::wl_seat::PX_PER_SCROLL;
use crate::ifs::wl_seat::wl_pointer::{PendingScroll, VERTICAL_SCROLL};
#[allow(dead_code)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@ -290,6 +291,7 @@ impl ContainerNode {
};
new.clone().set_workspace(&self.workspace.get());
new.clone().set_parent(self.clone());
new.set_visible(self.visible.get());
let num_children = self.num_children.fetch_add(1) + 1;
self.update_content_size();
let new_child_factor = 1.0 / num_children as f64;
@ -1134,24 +1136,36 @@ impl Node for ContainerNode {
Some(c) => c,
None => return,
};
let (have_mc, was_mc) = match self.mono_child.get() {
None => (false, false),
Some(mc) => (true, mc.node.id() == old.id()),
};
node.focus_history.set(None);
let link = node.append(ContainerChild {
node: new.clone(),
active: Cell::new(false),
body: Cell::new(node.body.get()),
content: Cell::new(node.content.get()),
content: Default::default(),
factor: Cell::new(node.factor.get()),
title: Default::default(),
title_rect: Cell::new(node.title_rect.get()),
focus_history: Cell::new(None),
});
let body = link.body.get();
drop(node);
let mut body = None;
if was_mc {
self.mono_child.set(Some(link.to_ref()));
body = Some(self.mono_body.get());
} else if !have_mc {
body = Some(link.body.get());
};
self.child_nodes.borrow_mut().insert(new.id(), link);
new.clone().set_parent(self.clone());
new.clone().set_workspace(&self.workspace.get());
let body = body.move_(self.abs_x1.get(), self.abs_y1.get());
new.clone().change_extents(&body);
if let Some(body) = body {
let body = body.move_(self.abs_x1.get(), self.abs_y1.get());
new.clone().change_extents(&body);
}
}
fn remove_child(self: Rc<Self>, child: &dyn Node) {

View file

@ -2,7 +2,6 @@ use {
crate::{
backend::Mode,
cursor::KnownCursor,
fixed::Fixed,
ifs::{
wl_output::WlOutputGlobal,
wl_seat::{NodeSeatState, WlSeatGlobal},
@ -36,6 +35,7 @@ pub struct OutputNode {
pub render_data: RefCell<OutputRenderData>,
pub state: Rc<State>,
pub is_dummy: bool,
pub status: CloneCell<Rc<String>>,
}
impl OutputNode {
@ -43,6 +43,7 @@ impl OutputNode {
let mut rd = self.render_data.borrow_mut();
rd.titles.clear();
rd.inactive_workspaces.clear();
rd.status = None;
let mut pos = 0;
let font = self.state.theme.font.borrow_mut();
let th = self.state.theme.title_height.get();
@ -54,13 +55,14 @@ impl OutputNode {
if th == 0 || ws.name.is_empty() {
break 'create_texture;
}
let title = match text::render_fitting(&ctx, th, &font, &ws.name, Color::GREY) {
Ok(t) => t,
Err(e) => {
log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e));
break 'create_texture;
}
};
let title =
match text::render_fitting(&ctx, th, &font, &ws.name, Color::GREY, false) {
Ok(t) => t,
Err(e) => {
log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e));
break 'create_texture;
}
};
let mut x = pos + 1;
if title.width() + 2 > title_width {
title_width = title.width() + 2;
@ -82,6 +84,29 @@ impl OutputNode {
}
pos += title_width;
}
'set_status: {
let ctx = match self.state.render_ctx.get() {
Some(ctx) => ctx,
_ => break 'set_status,
};
let status = self.status.get();
if status.is_empty() {
break 'set_status;
}
let title = match text::render_fitting(&ctx, th, &font, &status, Color::GREY, true) {
Ok(t) => t,
Err(e) => {
log::error!("Could not render status {}: {}", status, ErrorFmt(e));
break 'set_status;
}
};
let pos = self.global.pos.get().width() - title.width() - 1;
rd.status = Some(OutputTitle {
x: pos,
y: 0,
tex: title,
});
}
}
pub fn ensure_workspace(self: &Rc<Self>) -> Rc<WorkspaceNode> {
@ -106,7 +131,7 @@ impl OutputNode {
seat_state: Default::default(),
name: name.clone(),
output_link: Default::default(),
visible: Cell::new(false),
visible: Cell::new(true),
});
self.state.workspaces.set(name, workspace.clone());
workspace
@ -122,9 +147,9 @@ impl OutputNode {
if old.id == ws.id {
return;
}
old.visible.set(false);
old.set_visible(false);
}
ws.visible.set(true);
ws.set_visible(true);
ws.clone().change_extents(&self.workspace_rect());
}
@ -161,6 +186,7 @@ impl OutputNode {
fn change_extents_(&self, rect: &Rect) {
self.global.pos.set(*rect);
self.update_render_data();
if let Some(c) = self.workspace.get() {
c.change_extents(&self.workspace_rect());
}
@ -194,6 +220,11 @@ impl OutputNode {
}
FindTreeResult::Other
}
pub fn set_status(&self, status: &Rc<String>) {
self.status.set(status.clone());
self.update_render_data();
}
}
pub struct OutputTitle {
@ -207,6 +238,7 @@ pub struct OutputRenderData {
pub active_workspace: Rect,
pub inactive_workspaces: Vec<Rect>,
pub titles: Vec<OutputTitle>,
pub status: Option<OutputTitle>,
}
impl Debug for OutputNode {
@ -283,10 +315,6 @@ impl Node for OutputNode {
unimplemented!();
}
fn pointer_enter(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, _x: Fixed, _y: Fixed) {
seat.enter_output(&self)
}
fn pointer_focus(&self, seat: &Rc<WlSeatGlobal>) {
seat.set_known_cursor(KnownCursor::Default);
}

View file

@ -35,6 +35,7 @@ impl WorkspaceNode {
let pos = self.position.get();
container.clone().change_extents(&pos);
container.clone().set_workspace(self);
container.set_visible(self.visible.get());
self.container.set(Some(container.clone()));
}
}

View file

@ -1,11 +1,9 @@
# todo
- Container moving (mouse)
- Workspaces
- presentation time
- viewporter
- session lock
- xwayland
# done
@ -21,3 +19,5 @@
- Config
- Highlighting active
- Toplevel splitting
- Workspaces
- xwayland