Skip to content

Commit 3e720a5

Browse files
committed
sm: save and restore window positions
1 parent aeb1fe9 commit 3e720a5

10 files changed

Lines changed: 197 additions & 22 deletions

File tree

src/control_center/cc_window.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ pub fn show_window(behavior: &mut CcBehavior<'_>, ui: &mut Ui, window: &dyn Topl
358358
label(ui, "ID", &*data.identifier.get().to_string());
359359
label(ui, "Title", &*data.title.borrow());
360360
if let Some(w) = data.workspace.get() {
361-
label(ui, "Workspace", &w.name);
361+
label(ui, "Workspace", &*w.name);
362362
}
363363
match &data.kind {
364364
ToplevelType::Container => {

src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,13 @@ impl XdgToplevel {
407407
parent: Option<&XdgToplevel>,
408408
pos: Option<(&Rc<OutputNode>, i32, i32)>,
409409
) {
410+
if let Some(session) = self.toplevel_data.session.get()
411+
&& self
412+
.state
413+
.map_restore(self.clone(), &session, parent.is_some())
414+
{
415+
return;
416+
}
410417
if let Some(state) = self.state.initial_tile_state(&self.toplevel_data) {
411418
match state {
412419
TileState::Floating => {

src/it/tests/t0018_click_to_active_ws.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
2828
client.sync().await;
2929

3030
let name = ds.output.workspace.get().map(|ws| ws.name.clone());
31-
tassert_eq!(name.as_deref(), Some("1"));
31+
tassert_eq!(name.as_deref().map(String::as_str), Some("1"));
3232

3333
let pos = {
3434
let rd = ds.output.render_data.borrow_mut();
@@ -40,7 +40,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
4040
client.sync().await;
4141

4242
let name = ds.output.workspace.get().map(|ws| ws.name.clone());
43-
tassert_eq!(name.as_deref(), Some("2"));
43+
tassert_eq!(name.as_deref().map(String::as_str), Some("2"));
4444

4545
Ok(())
4646
}

src/sm.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use {
22
crate::{
33
client::Client,
4+
ifs::wl_output::OutputIdHash,
5+
rect::Rect,
46
sm::{
57
sm_jobs::{
68
SmPending, SmScheduled,
@@ -21,6 +23,7 @@ use {
2123
},
2224
sqlite::Sqlite,
2325
state::State,
26+
tree::{Node, OutputNode, ToplevelData, WorkspaceHash, WorkspaceNode},
2427
utils::{
2528
asyncevent::AsyncEvent,
2629
cell_ext::CellExt,
@@ -112,7 +115,6 @@ pub struct ToplevelSession {
112115
restore: bool,
113116
id: Cell<Option<ToplevelSessionId>>,
114117
owner: CloneCell<Option<Rc<dyn ToplevelSessionOwner>>>,
115-
#[expect(dead_code)]
116118
pub state: ToplevelSessionState,
117119
job: Cell<Option<ToplevelJob>>,
118120
}
@@ -400,12 +402,53 @@ impl Session {
400402
}
401403

402404
impl ToplevelSession {
403-
#[expect(dead_code)]
404405
fn state_changed(self: &Rc<Self>) {
405406
self.changed.set(true);
406407
self.schedule_job(false);
407408
}
408409

410+
pub fn set_workspace(self: &Rc<Self>, ws: &WorkspaceNode) {
411+
let hash = (!ws.is_dummy).then_some(ws.hash);
412+
if hash == self.state.workspace_hash.get() {
413+
return;
414+
}
415+
self.state.workspace_hash.set(hash);
416+
self.state
417+
.workspace
418+
.set((!ws.is_dummy).then_some(ws.name.clone()));
419+
self.state_changed();
420+
self.set_output(&ws.output.get());
421+
}
422+
423+
pub fn set_output(self: &Rc<Self>, on: &OutputNode) {
424+
let hash = (!on.is_dummy).then_some(on.global.output_id.hash);
425+
if hash == self.state.output.get() {
426+
return;
427+
}
428+
self.state.output.set(hash);
429+
self.state_changed();
430+
}
431+
432+
pub fn set_float_pos(self: &Rc<Self>, data: &ToplevelData) {
433+
let rect = if data.parent_is_float.get() {
434+
let on = data.output();
435+
if on.is_dummy {
436+
None
437+
} else {
438+
let on = on.node_absolute_position();
439+
let rect = data.desired_extents.get().move_(-on.x1(), -on.y1());
440+
Some(rect)
441+
}
442+
} else {
443+
None
444+
};
445+
if rect == self.state.floating_pos.get() {
446+
return;
447+
}
448+
self.state.floating_pos.set(rect);
449+
self.state_changed();
450+
}
451+
409452
fn schedule_job(self: &Rc<Self>, allow_changed: bool) {
410453
if self.job.is_some() {
411454
return;
@@ -451,13 +494,19 @@ impl ToplevelSession {
451494
return;
452495
}
453496
self.changed.take();
497+
let s = &self.state;
498+
let tid = &self.session.manager.thread_id;
454499
let job = self
455500
.session
456501
.manager
457502
.add_job(self, |job: &mut ToplevelUpdateJob| {
458503
job.work.session_id = session_id;
459504
job.work.id = id;
460-
job.work.data = SmToplevelIn {};
505+
job.work.data = SmToplevelIn {
506+
output: s.output.get(),
507+
workspace: s.workspace.get().map(|v| SendSyncRc::new(tid, &v)),
508+
floating_pos: s.floating_pos.get(),
509+
};
461510
});
462511
self.job.set(Some(ToplevelJob::Update(job)));
463512
return;
@@ -511,7 +560,12 @@ impl ToplevelSession {
511560
}
512561

513562
#[derive(Default, Clone)]
514-
pub struct ToplevelSessionState {}
563+
pub struct ToplevelSessionState {
564+
pub output: Cell<Option<OutputIdHash>>,
565+
pub floating_pos: Cell<Option<Rect>>,
566+
pub workspace: CloneCell<Option<Rc<String>>>,
567+
workspace_hash: Cell<Option<WorkspaceHash>>,
568+
}
515569

516570
impl Drop for Session {
517571
fn drop(&mut self) {

src/sm/sm_jobs/sm_toplevel_acquire.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ impl SqliteJob for ToplevelAcquireJob {
9393
cb.id.set(Some(o.id));
9494
let restored = o.restored.is_some();
9595
if let Some(v) = o.restored {
96-
let _ = v;
96+
cb.state.output.set(v.output);
97+
cb.state.workspace.set(v.workspace.map(Rc::new));
98+
cb.state.floating_pos.set(v.floating_pos);
9799
}
98100
if let Some(owner) = cb.owner.get() {
99101
owner.loaded(SessionGetStatus::from_restored(restored));

src/sm/sm_wire/sm_wire_toplevel.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
use {
2+
crate::{
3+
ifs::wl_output::OutputIdHash, rect::Rect, sm::sm_wire::WireRect,
4+
utils::send_sync_rc::SendSyncRc,
5+
},
26
bincode::{Deserializer, Options},
37
jay_config::_private::bincode_ops,
48
serde::{Deserialize, Serialize},
@@ -10,6 +14,8 @@ use {
1014
pub enum DeserializeToplevelError {
1115
#[error("Could not deserialize the V0 component")]
1216
DeserializeV0(#[source] bincode::Error),
17+
#[error("Could not deserialize the V1 component")]
18+
DeserializeV1(#[source] bincode::Error),
1319
}
1420

1521
pub fn deserialize_toplevel(data: &[u8]) -> Result<SmToplevelOut, DeserializeToplevelError> {
@@ -21,6 +27,9 @@ pub fn deserialize_toplevel(data: &[u8]) -> Result<SmToplevelOut, DeserializeTop
2127
}
2228
let mut wire = WireToplevel::default();
2329
wire.v0 = des!(DeserializeV0)?;
30+
if wire.v0.version >= 1 {
31+
wire.v1 = des!(DeserializeV1)?;
32+
}
2433
Ok(wire.into())
2534
}
2635

@@ -31,13 +40,23 @@ pub fn serialize_toplevel(data: &mut Vec<u8>, tl: &SmToplevelIn) {
3140
}
3241

3342
#[derive(Default)]
34-
pub struct SmToplevelIn {}
43+
pub struct SmToplevelIn {
44+
pub output: Option<OutputIdHash>,
45+
pub workspace: Option<SendSyncRc<String>>,
46+
pub floating_pos: Option<Rect>,
47+
}
3548

36-
pub struct SmToplevelOut {}
49+
#[derive(Default)]
50+
pub struct SmToplevelOut {
51+
pub output: Option<OutputIdHash>,
52+
pub workspace: Option<String>,
53+
pub floating_pos: Option<Rect>,
54+
}
3755

3856
#[derive(Default, Serialize)]
3957
struct WireToplevel<'a> {
4058
v0: WireToplevelV0<'a>,
59+
v1: WireToplevelV1<'a>,
4160
}
4261

4362
#[derive(Default, Serialize, Deserialize)]
@@ -46,19 +65,42 @@ struct WireToplevelV0<'a> {
4665
_phantom: PhantomData<&'a ()>,
4766
}
4867

68+
#[derive(Default, Serialize, Deserialize)]
69+
struct WireToplevelV1<'a> {
70+
output: Option<OutputIdHash>,
71+
#[serde(borrow)]
72+
workspace: Option<&'a str>,
73+
floating_pos: Option<WireRect>,
74+
}
75+
4976
impl<'a> From<&'a SmToplevelIn> for WireToplevel<'a> {
50-
fn from(_value: &'a SmToplevelIn) -> Self {
77+
fn from(value: &'a SmToplevelIn) -> Self {
5178
Self {
5279
v0: WireToplevelV0 {
53-
version: 0,
80+
version: 1,
5481
_phantom: Default::default(),
5582
},
83+
v1: value.into(),
84+
}
85+
}
86+
}
87+
88+
impl<'a> From<&'a SmToplevelIn> for WireToplevelV1<'a> {
89+
fn from(value: &'a SmToplevelIn) -> Self {
90+
Self {
91+
output: value.output,
92+
workspace: value.workspace.as_deref().map(|v| &**v),
93+
floating_pos: value.floating_pos.map(Into::into),
5694
}
5795
}
5896
}
5997

6098
impl From<WireToplevel<'_>> for SmToplevelOut {
61-
fn from(_value: WireToplevel<'_>) -> Self {
62-
Self {}
99+
fn from(value: WireToplevel<'_>) -> Self {
100+
Self {
101+
output: value.v1.output,
102+
workspace: value.v1.workspace.map(Into::into),
103+
floating_pos: value.v1.floating_pos.map(Into::into),
104+
}
63105
}
64106
}

src/state.rs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ use {
102102
renderer::Renderer,
103103
scale::Scale,
104104
security_context_acceptor::SecurityContextAcceptors,
105-
sm::SessionManager,
105+
sm::{SessionManager, ToplevelSession},
106106
sqlite::Sqlite,
107107
tagged_acceptor::TaggedAcceptors,
108108
theme::{BarPosition, Color, Theme, ThemeColor, ThemeSized},
@@ -118,6 +118,7 @@ use {
118118
utils::{
119119
asyncevent::AsyncEvent,
120120
bindings::Bindings,
121+
cell_ext::CellExt,
121122
clonecell::CloneCell,
122123
copyhashmap::CopyHashMap,
123124
errorfmt::ErrorFmt,
@@ -832,14 +833,67 @@ impl State {
832833
}
833834
}
834835

835-
pub fn ensure_map_workspace(&self, seat: Option<&Rc<WlSeatGlobal>>) -> Rc<WorkspaceNode> {
836+
pub fn get_map_output(&self, seat: Option<&Rc<WlSeatGlobal>>) -> Rc<OutputNode> {
836837
seat.cloned()
837838
.or_else(|| self.seat_queue.last().map(|s| s.deref().clone()))
838839
.map(|s| s.get_fallback_output())
839840
.or_else(|| self.root.outputs.lock().values().next().cloned())
840841
.or_else(|| self.dummy_output.get())
841842
.unwrap()
842-
.ensure_workspace()
843+
}
844+
845+
pub fn ensure_map_workspace(&self, seat: Option<&Rc<WlSeatGlobal>>) -> Rc<WorkspaceNode> {
846+
self.get_map_output(seat).ensure_workspace()
847+
}
848+
849+
pub fn map_restore(
850+
self: &Rc<Self>,
851+
node: Rc<dyn ToplevelNode>,
852+
session: &Rc<ToplevelSession>,
853+
has_parent: bool,
854+
) -> bool {
855+
let s = &session.state;
856+
let on = || {
857+
if let Some(o) = s.output.get() {
858+
for on in self.root.outputs.lock().values() {
859+
if on.global.output_id.hash == o {
860+
return on.clone();
861+
}
862+
}
863+
}
864+
self.get_map_output(None)
865+
};
866+
let ws = || {
867+
let Some(name) = s.workspace.get() else {
868+
return on().ensure_workspace();
869+
};
870+
if let Some(w) = self.workspaces.get(&*name) {
871+
return w;
872+
}
873+
on().create_workspace(&name)
874+
};
875+
let ws = if let Some(pos) = session.state.floating_pos.get() {
876+
let ws = ws();
877+
let op = ws.output.get().node_absolute_position();
878+
self.map_floating(
879+
node.clone(),
880+
pos.width(),
881+
pos.height(),
882+
&ws,
883+
Some((pos.x1() + op.x1(), pos.y1() + op.y1())),
884+
);
885+
ws
886+
} else if !has_parent && (s.workspace.is_some() || s.output.is_some()) {
887+
let ws = ws();
888+
self.map_tiled_on(node.clone(), &ws);
889+
ws
890+
} else {
891+
return false;
892+
};
893+
if ws.output.get().workspace.get().map(|w| w.id) != Some(ws.id) {
894+
node.tl_data().request_attention(&*node);
895+
}
896+
true
843897
}
844898

845899
pub fn map_tiled(self: &Rc<Self>, node: Rc<dyn ToplevelNode>) {

src/tree/output.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ impl OutputNode {
707707
wh.handle_destroyed();
708708
}
709709
old.clear();
710-
self.state.workspaces.remove(&old.name);
710+
self.state.workspaces.remove(&*old.name);
711711
} else {
712712
old.set_visible(false);
713713
old.flush_jay_workspaces();

0 commit comments

Comments
 (0)