Rewild Your Web
18
fork

Configure Feed

Select the types of activity you want to include in your feed.

shell: save and restore window position and size

Signed-off-by: webbeef <me@webbeef.org>

+185 -69
+16
crates/beaver_shell/src/browser_window.rs
··· 343 343 if let Some(webview) = self.first_webview() { 344 344 webview.resize(new_size); 345 345 } 346 + self.save_window_state(); 347 + }, 348 + WindowEvent::Moved(_) => { 349 + self.save_window_state(); 346 350 }, 347 351 WindowEvent::ModifiersChanged(modifiers) => { 348 352 self.modifiers_state.set(modifiers.state()); ··· 353 357 }, 354 358 _ => (), 355 359 } 360 + } 361 + 362 + fn save_window_state(&self) { 363 + let size = self.window.inner_size(); 364 + let position = self.window.outer_position().unwrap_or_default(); 365 + crate::window_state::WindowState { 366 + width: size.width, 367 + height: size.height, 368 + x: position.x, 369 + y: position.y, 370 + } 371 + .save(&crate::config_dir()); 356 372 } 357 373 358 374 fn maybe_notify_keyboard_event(&self, keyboard_event: KeyboardEvent) {
+1 -1
crates/beaver_shell/src/content_blocker.rs
··· 15 15 UrlWithBlobClaim, fetch_async, 16 16 }; 17 17 18 - const CACHE_FILE: &str = "adblock-engine.bin"; 18 + const CACHE_FILE: &str = "adblock_engine.bin"; 19 19 const DB_FILE: &str = "content_blocker.db"; 20 20 const EASYLIST_URL: &str = "https://easylist.to/easylist/easylist.txt"; 21 21 const BUNDLED_EASYLIST: &str = include_str!("../resources/easylist.txt");
+31 -5
crates/beaver_shell/src/main.rs
··· 11 11 mod touch_event_simulator; 12 12 #[cfg(feature = "status-tray")] 13 13 mod tray; 14 + mod window_state; 14 15 15 16 use std::cell::{Cell, RefCell}; 16 17 use std::collections::HashMap; ··· 70 71 } 71 72 } 72 73 73 - /// Load Servo preferences from `servo-prefs.json` in the config directory, 74 + /// Load Servo preferences from `servo_prefs.json` in the config directory, 74 75 /// merged with hardcoded defaults for beaver. 75 76 fn load_servo_prefs(config_dir: Option<PathBuf>) -> Preferences { 76 77 // Start with beaver's defaults. ··· 83 84 }; 84 85 85 86 // Try to load saved preferences and merge on top. 86 - if let Some(path) = config_dir.map(|d| d.join("servo-prefs.json")) { 87 + if let Some(path) = config_dir.map(|d| d.join("servo_prefs.json")) { 87 88 if let Ok(content) = std::fs::read_to_string(&path) { 88 89 match serde_json::from_str::<Preferences>(&content) { 89 90 Ok(saved) => { ··· 91 92 info!("Loaded Servo preferences from {}", path.display()); 92 93 }, 93 94 Err(e) => { 94 - warn!("Failed to parse servo-prefs.json: {e}"); 95 + warn!("Failed to parse servo_prefs.json: {e}"); 95 96 }, 96 97 } 97 98 } ··· 180 181 /// 1. Clear all windows (which hold WebViews that reference Servo) 181 182 /// 2. Set a dummy delegate to break the AppState <-> Servo cycle 182 183 fn shutdown(&self) { 184 + if let Some(window) = self.focused_window.borrow().as_ref() { 185 + let size = window.window.inner_size(); 186 + let position = window.window.outer_position().unwrap_or_default(); 187 + window_state::WindowState { 188 + width: size.width, 189 + height: size.height, 190 + x: position.x, 191 + y: position.y, 192 + } 193 + .save(&config_dir()); 194 + } 183 195 // Clear focused_window reference 184 196 *self.focused_window.borrow_mut() = None; 185 197 // Clear windows - this drops WebViews which hold Servo references ··· 251 263 Some(PrefValue::Bool(true)) 252 264 ); 253 265 254 - if simulate_touch { 266 + if let Some(state) = window_state::WindowState::load(&config_dir()) { 267 + info!( 268 + "[WindowState] Restoring {}x{} at ({}, {})", 269 + state.width, state.height, state.x, state.y 270 + ); 271 + window_attributes = window_attributes 272 + .with_inner_size(winit::dpi::PhysicalSize { 273 + width: state.width, 274 + height: state.height, 275 + }) 276 + .with_position(winit::dpi::PhysicalPosition { 277 + x: state.x, 278 + y: state.y, 279 + }); 280 + } else if simulate_touch { 255 281 window_attributes = window_attributes.with_inner_size(winit::dpi::PhysicalSize { 256 282 width: 480, 257 283 height: 860, ··· 910 936 servo.setup_logging(); 911 937 912 938 // Register observer to persist Servo preferences on change. 913 - if let Some(path) = config_dir().map(|d| d.join("servo-prefs.json")) { 939 + if let Some(path) = config_dir().map(|d| d.join("servo_prefs.json")) { 914 940 servo::prefs::add_observer(Box::new(ServoPrefsPersister { path })); 915 941 } 916 942
+30
crates/beaver_shell/src/window_state.rs
··· 1 + // SPDX-License-Identifier: AGPL-3.0-or-later 2 + 3 + use std::fs; 4 + use std::path::PathBuf; 5 + 6 + use serde::{Deserialize, Serialize}; 7 + 8 + #[derive(Serialize, Deserialize)] 9 + pub struct WindowState { 10 + pub width: u32, 11 + pub height: u32, 12 + pub x: i32, 13 + pub y: i32, 14 + } 15 + 16 + impl WindowState { 17 + pub fn load(config_dir: &Option<PathBuf>) -> Option<Self> { 18 + let path = config_dir.as_ref()?.join("window_state.json"); 19 + let content = fs::read_to_string(path).ok()?; 20 + serde_json::from_str(&content).ok() 21 + } 22 + 23 + pub fn save(&self, config_dir: &Option<PathBuf>) { 24 + let Some(dir) = config_dir else { return }; 25 + let path = dir.join("window_state.json"); 26 + if let Ok(json) = serde_json::to_string(self) { 27 + let _ = fs::write(path, json); 28 + } 29 + } 30 + }
+107 -63
ui/system/remote/index.css
··· 1 1 /* SPDX-License-Identifier: AGPL-3.0-or-later */ 2 2 3 3 /* 4 - * Beaver Remote Control — handheld touch UI for controlling a media center. 5 - * Uses the standard Beaver theme tokens so it adapts to the user's chosen theme. 4 + * Beaver Remote Control — a warm, tactile phone interface 5 + * for controlling The Hearth media center. 6 + * Feels like holding a handcrafted wooden remote. 6 7 */ 7 8 8 9 * { ··· 20 21 overflow: hidden; 21 22 user-select: none; 22 23 -webkit-user-select: none; 24 + -webkit-tap-highlight-color: transparent; 23 25 } 24 26 25 27 .screen { ··· 36 38 /* ===== Device Selector ===== */ 37 39 38 40 .selector-header { 39 - padding: 4em 2em 2em; 41 + padding: 3.5em 2em 1.5em; 40 42 text-align: center; 41 43 } 42 44 43 45 .app-title { 44 46 font-family: Pacifico, cursive; 45 - font-size: 2.8em; 47 + font-size: 2.4em; 46 48 color: var(--color-text); 47 49 line-height: 1.1; 48 50 } 49 51 50 52 .app-subtitle { 51 - font-size: 0.95em; 53 + font-size: 0.85em; 52 54 color: var(--color-text-tertiary); 53 - margin-top: 0.5em; 54 - letter-spacing: 0.03em; 55 + margin-top: 0.4em; 56 + letter-spacing: 0.04em; 57 + text-transform: uppercase; 55 58 } 56 59 57 60 .device-list { ··· 67 70 gap: 1.2em; 68 71 padding: 4em 2em; 69 72 color: var(--color-text-tertiary); 70 - font-size: 0.95em; 73 + font-size: 0.9em; 71 74 } 72 75 73 76 .spinner { ··· 83 86 .device-item { 84 87 display: flex; 85 88 align-items: center; 86 - gap: 1.2em; 87 - padding: 1.3em 1.5em; 89 + gap: 1em; 90 + padding: 1.2em 1.4em; 88 91 background: var(--bg-surface); 89 92 border-radius: var(--radius-md); 90 - margin-bottom: 0.6em; 93 + margin-bottom: 0.5em; 91 94 cursor: pointer; 92 95 transition: 93 - background var(--transition-fast), 94 - transform 0.1s cubic-bezier(0.16, 1, 0.3, 1); 96 + background 0.18s cubic-bezier(0.16, 1, 0.3, 1), 97 + transform 0.12s cubic-bezier(0.16, 1, 0.3, 1); 95 98 border: 1px solid var(--color-border); 96 99 } 97 100 ··· 101 104 } 102 105 103 106 .device-item lucide-icon { 104 - font-size: 1.6em; 107 + font-size: 1.4em; 105 108 color: var(--color-primary); 106 109 } 107 110 ··· 111 114 112 115 .device-item-name { 113 116 font-weight: var(--font-weight-bold); 114 - font-size: 1.1em; 117 + font-size: 1em; 115 118 } 116 119 117 120 .device-item-status { 118 121 font-size: var(--font-size-sm); 119 122 color: var(--color-text-secondary); 120 - margin-top: 0.15em; 123 + margin-top: 0.1em; 121 124 } 122 125 123 126 .no-devices { ··· 125 128 padding: 4em 2em; 126 129 color: var(--color-text-tertiary); 127 130 line-height: 1.6; 131 + font-size: 0.9em; 128 132 } 129 133 130 - /* ===== Guest Connect (device list) ===== */ 134 + /* ===== Guest Connect ===== */ 131 135 132 136 .device-item-guest { 133 137 display: flex; 134 138 align-items: center; 135 - gap: 1.2em; 136 - padding: 1.3em 1.5em; 139 + gap: 1em; 140 + padding: 1.2em 1.4em; 137 141 background: none; 138 142 border-radius: var(--radius-md); 139 - margin-bottom: 0.6em; 143 + margin-bottom: 0.5em; 140 144 cursor: pointer; 141 145 transition: 142 - background var(--transition-fast), 143 - transform 0.1s cubic-bezier(0.16, 1, 0.3, 1); 146 + background 0.18s cubic-bezier(0.16, 1, 0.3, 1), 147 + transform 0.12s cubic-bezier(0.16, 1, 0.3, 1); 144 148 border: 1px dashed var(--color-border); 145 149 } 146 150 ··· 150 154 } 151 155 152 156 .device-item-guest lucide-icon { 153 - font-size: 1.6em; 157 + font-size: 1.4em; 154 158 color: var(--color-text-secondary); 155 159 } 156 160 157 - /* ===== PIN Entry Screen ===== */ 161 + /* ===== PIN Entry ===== */ 158 162 159 163 .pin-form { 160 164 display: flex; ··· 177 181 font-family: var(--font-family-base); 178 182 font-weight: var(--font-weight-bold); 179 183 caret-color: var(--color-primary); 180 - transition: border-color var(--transition-fast); 184 + transition: border-color 0.18s cubic-bezier(0.16, 1, 0.3, 1); 181 185 } 182 186 183 187 .pin-input:focus { ··· 207 211 font-size: 1em; 208 212 cursor: pointer; 209 213 transition: 210 - background var(--transition-fast), 214 + background 0.18s cubic-bezier(0.16, 1, 0.3, 1), 211 215 transform 0.1s cubic-bezier(0.16, 1, 0.3, 1); 212 216 } 213 217 ··· 236 240 .connection-bar { 237 241 display: flex; 238 242 align-items: center; 239 - gap: 0.8em; 240 - padding: 0.9em 1.5em; 243 + gap: 0.6em; 244 + padding: 0.7em 1.2em; 241 245 background: var(--bg-header); 242 246 border-bottom: 1px solid var(--color-border); 243 247 } 244 248 245 249 .connected-icon { 246 250 color: var(--color-primary); 247 - font-size: 1em; 251 + font-size: 0.9em; 248 252 } 249 253 250 254 #connected-name { ··· 261 265 padding: 0.5em; 262 266 cursor: pointer; 263 267 border-radius: 50%; 264 - transition: background var(--transition-fast); 268 + transition: background 0.18s cubic-bezier(0.16, 1, 0.3, 1); 265 269 } 266 270 267 271 .disconnect-btn:active { ··· 271 275 /* ===== Now Playing ===== */ 272 276 273 277 .now-playing { 274 - padding: 1.2em 1.5em; 278 + padding: 1em 1.4em; 275 279 background: var(--bg-menu); 276 280 border-bottom: 1px solid var(--color-border); 277 281 } 278 282 279 283 .now-playing-info { 280 284 text-align: center; 281 - margin-bottom: 1em; 285 + margin-bottom: 0.8em; 282 286 } 283 287 284 288 .np-title { 285 289 font-weight: var(--font-weight-bold); 286 - font-size: 1.05em; 290 + font-size: 1em; 287 291 white-space: nowrap; 288 292 overflow: hidden; 289 293 text-overflow: ellipsis; ··· 292 296 .np-artist { 293 297 font-size: var(--font-size-sm); 294 298 color: var(--color-text-secondary); 295 - margin-top: 0.15em; 299 + margin-top: 0.1em; 296 300 } 297 301 298 302 .transport-controls { 299 303 display: flex; 300 304 align-items: center; 301 305 justify-content: center; 302 - gap: 1em; 306 + gap: 0.8em; 303 307 } 304 308 305 309 .transport-btn { ··· 314 318 color: var(--color-text); 315 319 cursor: pointer; 316 320 transition: 317 - background 0.12s, 318 - transform 0.1s cubic-bezier(0.16, 1, 0.3, 1); 321 + background 0.05s cubic-bezier(0.16, 1, 0.3, 1), 322 + transform 0.05s cubic-bezier(0.16, 1, 0.3, 1); 319 323 } 320 324 321 325 .transport-btn:active { 322 326 background: var(--bg-hover); 323 - transform: scale(0.9); 327 + transform: translateY(1px); 324 328 } 325 329 326 330 .transport-btn.play-btn { 327 331 width: 3.5em; 328 332 height: 3.5em; 329 333 background: var(--bg-surface); 334 + border: 1px solid var(--color-border); 335 + box-shadow: 336 + 0 2px 0 var(--color-border), 337 + 0 2px 4px var(--color-shadow); 338 + } 339 + 340 + .transport-btn.play-btn:active { 341 + box-shadow: none; 342 + transform: translateY(2px); 330 343 } 331 344 332 345 .transport-btn lucide-icon { ··· 334 347 } 335 348 336 349 .transport-btn.play-btn lucide-icon { 337 - font-size: 1.5em; 350 + font-size: 1.4em; 338 351 } 339 352 340 353 /* ===== D-Pad ===== */ ··· 351 364 display: grid; 352 365 grid-template-columns: 1fr 1fr 1fr; 353 366 grid-template-rows: 1fr 1fr 1fr; 354 - gap: 0.6em; 355 - width: min(300px, 75vw); 367 + gap: 6px; 368 + width: min(280px, 72vw); 356 369 aspect-ratio: 1; 357 370 } 358 371 ··· 362 375 justify-content: center; 363 376 background: var(--bg-surface); 364 377 border: 1px solid var(--color-border); 365 - border-radius: var(--radius-md); 366 378 color: var(--color-text); 367 379 cursor: pointer; 368 380 font-family: var(--font-family-base); 369 381 font-size: 1em; 382 + box-shadow: 383 + 0 2px 0 var(--color-border), 384 + 0 2px 4px var(--color-shadow); 370 385 transition: 371 - background 0.1s, 372 - transform 0.08s cubic-bezier(0.16, 1, 0.3, 1); 373 - -webkit-tap-highlight-color: transparent; 386 + background 0.05s cubic-bezier(0.16, 1, 0.3, 1), 387 + box-shadow 0.05s cubic-bezier(0.16, 1, 0.3, 1), 388 + transform 0.05s cubic-bezier(0.16, 1, 0.3, 1); 374 389 } 375 390 376 391 .dpad-btn:active { 377 392 background: var(--color-menu-item-hover); 378 - transform: scale(0.92); 393 + box-shadow: none; 394 + transform: translateY(2px); 379 395 } 380 396 381 397 .dpad-btn lucide-icon { 382 - font-size: 2em; 398 + font-size: 1.8em; 399 + } 400 + 401 + .dpad-btn.up { 402 + grid-column: 2; 403 + grid-row: 1; 404 + border-radius: var(--radius-md) var(--radius-md) 4px 4px; 405 + } 406 + 407 + .dpad-btn.down { 408 + grid-column: 2; 409 + grid-row: 3; 410 + border-radius: 4px 4px var(--radius-md) var(--radius-md); 411 + } 412 + 413 + .dpad-btn.left { 414 + grid-column: 1; 415 + grid-row: 2; 416 + border-radius: var(--radius-md) 4px 4px var(--radius-md); 383 417 } 384 418 385 - .dpad-btn.up { grid-column: 2; grid-row: 1; border-radius: var(--radius-md) var(--radius-md) 4px 4px; } 386 - .dpad-btn.down { grid-column: 2; grid-row: 3; border-radius: 4px 4px var(--radius-md) var(--radius-md); } 387 - .dpad-btn.left { grid-column: 1; grid-row: 2; border-radius: var(--radius-md) 4px 4px var(--radius-md); } 388 - .dpad-btn.right { grid-column: 3; grid-row: 2; border-radius: 4px var(--radius-md) var(--radius-md) 4px; } 419 + .dpad-btn.right { 420 + grid-column: 3; 421 + grid-row: 2; 422 + border-radius: 4px var(--radius-md) var(--radius-md) 4px; 423 + } 389 424 390 425 .dpad-btn.ok { 391 426 grid-column: 2; ··· 394 429 border-color: var(--color-primary); 395 430 color: var(--color-text-on-header); 396 431 font-weight: var(--font-weight-bold); 397 - font-size: 1.2em; 432 + font-size: 1.1em; 398 433 border-radius: 50%; 434 + letter-spacing: 0.05em; 435 + box-shadow: 436 + 0 2px 0 oklch(35% 0.08 155), 437 + 0 2px 6px var(--color-shadow); 399 438 } 400 439 401 440 .dpad-btn.ok:active { 402 - opacity: 0.85; 403 - transform: scale(0.9); 441 + box-shadow: none; 442 + transform: translateY(2px); 443 + opacity: 0.9; 404 444 } 405 445 406 446 /* ===== Utility Bar ===== */ ··· 408 448 .utility-bar { 409 449 display: flex; 410 450 justify-content: center; 411 - gap: 1em; 412 - padding: 1em 1.5em 2.5em; 451 + gap: 0.8em; 452 + padding: 0.8em 1.5em 2em; 413 453 } 414 454 415 455 .utility-btn { 416 456 display: flex; 417 457 align-items: center; 418 458 justify-content: center; 419 - width: 3.5em; 420 - height: 3.5em; 459 + width: 3.2em; 460 + height: 3.2em; 421 461 background: var(--bg-surface); 422 462 border: 1px solid var(--color-border); 423 463 color: var(--color-text-secondary); 424 464 cursor: pointer; 425 465 border-radius: var(--radius-md); 426 466 font-family: var(--font-family-base); 467 + box-shadow: 468 + 0 2px 0 var(--color-border), 469 + 0 2px 4px var(--color-shadow); 427 470 transition: 428 - background 0.1s, 429 - transform 0.08s cubic-bezier(0.16, 1, 0.3, 1); 430 - -webkit-tap-highlight-color: transparent; 471 + background 0.05s cubic-bezier(0.16, 1, 0.3, 1), 472 + box-shadow 0.05s cubic-bezier(0.16, 1, 0.3, 1), 473 + transform 0.05s cubic-bezier(0.16, 1, 0.3, 1); 431 474 } 432 475 433 476 .utility-btn:active { 434 477 background: var(--color-menu-item-hover); 435 - transform: scale(0.9); 478 + box-shadow: none; 479 + transform: translateY(2px); 436 480 } 437 481 438 482 .utility-btn lucide-icon { 439 - font-size: 1.5em; 483 + font-size: 1.3em; 440 484 }