Skip to content

Commit 38c652b

Browse files
committed
Refactor linux-pop-extension into Cosmic Containers applet for managing Docker containers. Update AGENTS.md and README.md for improved clarity on functionality and usage. Enhance Cargo.toml with new dependencies and features, and modify CHANGELOG.md to reflect added features and changes. Revise desktop entry and metainfo for branding consistency.
1 parent d7e0112 commit 38c652b

12 files changed

Lines changed: 331 additions & 47 deletions

File tree

‎AGENTS.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
1111
## Overview
1212

13-
Rust crate (`linux-pop-extension`): **COSMIC panel applet** for Pop!_OS via `libcosmic` (`cosmic::applet::run`). Binary requires feature `applet` and system Wayland/GTK-related dev packages. See `README.md` for install and “Add widget” steps.
13+
Rust crate (`linux-pop-extension`): **Cosmic Containers** — COSMIC panel applet for Pop!_OS that lists and starts/stops Docker containers via CLI (`libcosmic`, `cosmic::applet::run`). Binary requires feature `applet`, Docker in PATH, and system Wayland/GTK-related dev packages. See `README.md` for install and “Add widget” steps.
1414

1515
**Chat language**: Reply to the user in **Ukrainian** unless they ask for another language. Keep code, identifiers, commits, and this file in **English**.
1616

@@ -63,16 +63,17 @@ Prefer `cargo check` for fast feedback during edits; run `cargo build` or `cargo
6363
linux-pop-extension/
6464
AGENTS.md # This file
6565
Cargo.toml # `applet` feature → libcosmic
66-
src/lib.rs # APP_ID, PANEL_EMOJI (unit tests)
66+
src/lib.rs # APP_ID, ContainerSummary, parse_ps_output
6767
src/main.rs # cosmic::applet::run
68-
src/app.rs # Application impl (panel view)
68+
src/app.rs # Application impl (panel + popup)
69+
src/services/docker.rs # async docker CLI
6970
resources/ # .desktop, metainfo, icon
7071
scripts/install-local.sh
7172
.githooks/
7273
.cursor/skills/
7374
```
7475

75-
Panel UI lives in `src/app.rs`; shared constants in `src/lib.rs`. Register applets with `X-CosmicApplet=true` in `resources/*.desktop`.
76+
Panel UI lives in `src/app.rs`; parsing and types in `src/lib.rs`; Docker in `src/services/docker.rs`. Register applets with `X-CosmicApplet=true` in `resources/*.desktop`.
7677

7778
## Testing
7879

‎CHANGELOG.md‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
- Click popup on panel smiley (toggle open/close with greeting text)
13-
- COSMIC panel applet using `libcosmic` (smiley `😀` on the panel)
12+
- **Cosmic Containers**: panel badge with running Docker container count
13+
- Popup list from `docker ps -a` (async CLI, non-blocking UI)
14+
- **Play** / **Stop** per container with automatic list refresh
15+
- Docker error hints (missing CLI, daemon, permission denied)
16+
- `src/services/docker.rs` and `ContainerSummary` parser with unit tests
17+
18+
### Changed
19+
20+
- Replaced smiley demo with Docker manager UI
21+
- Desktop/metainfo branding: **Cosmic Containers**
22+
23+
### Added (initial scaffold)
24+
25+
- COSMIC panel applet using `libcosmic` with popup support
1426
- Desktop entry and AppStream metainfo for **Add widget** in COSMIC settings
1527
- `scripts/install-local.sh` for `~/.local` installation
1628
- Optional `applet` feature so library tests build without GUI system libraries

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "linux-pop-extension"
33
version = "0.1.0"
44
edition = "2024"
55
license = "MIT"
6-
description = "COSMIC panel applet for Pop!_OS (smiley starter widget)"
6+
description = "COSMIC panel applet for Pop!_OS — Docker container manager on the panel"
77
readme = "README.md"
88

99
[features]
@@ -19,6 +19,7 @@ path = "src/main.rs"
1919
required-features = ["applet"]
2020

2121
[dependencies]
22+
tokio = { version = "1", features = ["process", "rt"] }
2223
libcosmic = { git = "https://github.com/pop-os/libcosmic.git", optional = true, features = [
2324
"applet",
2425
"tokio",

‎README.md‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
# linux-pop-extension
1+
# Cosmic Containers (linux-pop-extension)
22

3-
COSMIC panel applet for **Pop!_OS** (COSMIC desktop). Starter widget that shows a smiley on the panel and can be added from system settings.
3+
COSMIC panel applet for **Pop!_OS**: manage Docker containers from the panel — running count at a glance, list in a popup, **Play** / **Stop** without keeping a terminal open on `docker ps`.
44

55
## Requirements
66

77
- **Pop!_OS 24.04+** (or another distro with COSMIC panel)
8+
- **Docker** CLI (`docker` in `PATH`, daemon running, user in `docker` group or rootless Docker)
89
- Rust 1.85 or newer (edition 2024)
910
- System libraries for building `libcosmic`:
1011

@@ -35,8 +36,17 @@ This installs to `~/.local` (binary, `.desktop`, AppStream metainfo, icon).
3536

3637
1. Restart the panel or log out and back in (if the widget list does not update).
3738
2. Open **Settings → Desktop → Panel → Widgets → Add widget**.
38-
3. Select **Linux Pop Extension** → **Add**.
39-
4. The panel shows **😀**.
39+
3. Select **Cosmic Containers** (desktop id: `dev.linuxpop.Extension`) → **Add**.
40+
4. The panel shows the number of **running** containers (`0`, `1`, …). Click to open the list.
41+
42+
## Usage
43+
44+
| Panel | Meaning |
45+
|-------|---------|
46+
| `N` | `N` containers in `running` state |
47+
| `!` | Docker error (not installed, daemon down, or permission denied) |
48+
49+
In the popup: **Refresh**, **Play** (start stopped container), **Stop** (stop running container).
4050

4151
## Development
4252

@@ -67,7 +77,9 @@ cargo test --lib --no-default-features && cargo test --all-features
6777
| Item | Description |
6878
|------|-------------|
6979
| `APP_ID` | Desktop entry ID: `dev.linuxpop.Extension` |
70-
| `PANEL_EMOJI` | Panel label text (`😀`) |
80+
| `ContainerSummary` | Parsed `name` + `state` from `docker ps` |
81+
| `parse_ps_output` | Parse CLI stdout (`name\|state` per line) |
82+
| `running_count` | Count containers with `running` state |
7183

7284
## Contributing
7385

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
[Desktop Entry]
2-
Name=Linux Pop Extension
3-
Name[uk]=Linux Pop Extension
4-
Comment=Panel widget with smiley and click popup
5-
Comment[uk]=Віджет панелі зі смайликом і popup по кліку
2+
Name=Cosmic Containers
3+
Name[uk]=Cosmic Containers
4+
Comment=Docker container manager on the COSMIC panel
5+
Comment[uk]=Менеджер Docker-контейнерів на панелі COSMIC
66
Type=Application
77
Icon=dev.linuxpop.Extension
88
Exec=linux-pop-extension
99
Terminal=false
1010
StartupNotify=true
1111
Categories=COSMIC
12-
Keywords=COSMIC
12+
Keywords=COSMIC;Docker;containers;
1313
NoDisplay=true
1414
X-CosmicApplet=true

‎resources/dev.linuxpop.Extension.metainfo.xml‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,24 @@
33
<id>dev.linuxpop.Extension</id>
44
<metadata_license>CC0-1.0</metadata_license>
55
<project_license>MIT</project_license>
6-
<name>Linux Pop Extension</name>
7-
<summary>Simple COSMIC panel widget (smiley starter)</summary>
6+
<name>Cosmic Containers</name>
7+
<summary>Docker container manager for the COSMIC panel</summary>
88
<description>
9-
<p>A starter panel applet for Pop!_OS COSMIC that shows a smiley on the panel.</p>
9+
<p>Panel applet for Pop!_OS COSMIC: shows how many Docker containers are running, lists all containers in a popup, and lets you start or stop them without a terminal.</p>
10+
<p>Requires the Docker CLI and a running Docker daemon.</p>
1011
</description>
1112
<launchable type="desktop-id">dev.linuxpop.Extension.desktop</launchable>
1213
<provides>
1314
<binary>linux-pop-extension</binary>
1415
</provides>
1516
<categories>
1617
<category>COSMIC</category>
18+
<category>Development</category>
1719
</categories>
1820
<keywords>
1921
<keyword>COSMIC</keyword>
2022
<keyword>applet</keyword>
21-
<keyword>widget</keyword>
23+
<keyword>Docker</keyword>
24+
<keyword>containers</keyword>
2225
</keywords>
2326
</component>

‎src/app.rs‎

Lines changed: 121 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,62 @@
11
use cosmic::app::{Core, Task};
2+
use cosmic::iced::Length;
3+
use cosmic::iced::alignment::Alignment;
24
use cosmic::iced::theme::Style;
35
use cosmic::iced::window;
46
use cosmic::iced::window::Id;
57
use cosmic::prelude::*;
68
use cosmic::surface::action::{app_popup, destroy_popup};
79
use cosmic::widget;
8-
use linux_pop_extension::{PANEL_EMOJI, POPUP_GREETING};
10+
use linux_pop_extension::services::docker;
11+
use linux_pop_extension::{ContainerSummary, running_count};
912

1013
/// COSMIC panel applet state.
1114
#[derive(Default)]
1215
pub struct App {
1316
core: Core,
1417
popup: Option<Id>,
18+
containers: Vec<ContainerSummary>,
19+
loading: bool,
20+
error: Option<String>,
1521
}
1622

17-
/// Messages from the panel button and popup lifecycle.
23+
/// Messages from the panel button, popup, and Docker CLI results.
1824
#[derive(Clone, Debug)]
1925
pub enum Message {
2026
PopupClosed(Id),
2127
TogglePopup,
28+
Refresh,
29+
ContainersLoaded(Result<Vec<ContainerSummary>, String>),
30+
Start(String),
31+
Stop(String),
32+
}
33+
34+
impl App {
35+
fn panel_label(&self) -> String {
36+
if self.error.is_some() && self.containers.is_empty() {
37+
return "!".to_string();
38+
}
39+
format!("{}", running_count(&self.containers))
40+
}
41+
42+
fn fetch_containers_task() -> Task<Message> {
43+
Task::perform(docker::list_containers(), |result| {
44+
cosmic::Action::App(Message::ContainersLoaded(result))
45+
})
46+
}
47+
48+
fn apply_loaded(&mut self, result: Result<Vec<ContainerSummary>, String>) {
49+
self.loading = false;
50+
match result {
51+
Ok(containers) => {
52+
self.containers = containers;
53+
self.error = None;
54+
}
55+
Err(err) => {
56+
self.error = Some(err);
57+
}
58+
}
59+
}
2260
}
2361

2462
impl cosmic::Application for App {
@@ -37,7 +75,13 @@ impl cosmic::Application for App {
3775
}
3876

3977
fn init(core: Core, _flags: Self::Flags) -> (Self, Task<Self::Message>) {
40-
(Self { core, popup: None }, Task::none())
78+
(
79+
Self {
80+
core,
81+
..Default::default()
82+
},
83+
Self::fetch_containers_task(),
84+
)
4185
}
4286

4387
fn on_close_requested(&self, id: window::Id) -> Option<Self::Message> {
@@ -57,29 +101,51 @@ impl cosmic::Application for App {
57101
cosmic::app::Action::Surface(destroy_popup(id)),
58102
));
59103
}
60-
return cosmic::task::message(cosmic::Action::Cosmic(
104+
self.loading = true;
105+
let fetch = Self::fetch_containers_task();
106+
let open = cosmic::task::message(cosmic::Action::Cosmic(
61107
cosmic::app::Action::Surface(app_popup::<App>(
62108
|state: &mut App| {
63109
let new_id = Id::unique();
64110
state.popup = Some(new_id);
65111
state.core.applet.get_popup_settings(
66112
state.core.main_window_id().unwrap(),
67113
new_id,
68-
None,
114+
Some((360, 420)),
69115
None,
70116
None,
71117
)
72118
},
73119
Some(Box::new(popup_view)),
74120
)),
75121
));
122+
return Task::batch([open, fetch]);
123+
}
124+
Message::Refresh => {
125+
self.loading = true;
126+
return Self::fetch_containers_task();
127+
}
128+
Message::ContainersLoaded(result) => {
129+
self.apply_loaded(result);
130+
}
131+
Message::Start(name) => {
132+
self.loading = true;
133+
return Task::perform(docker::start_then_list(name), |result| {
134+
cosmic::Action::App(Message::ContainersLoaded(result))
135+
});
136+
}
137+
Message::Stop(name) => {
138+
self.loading = true;
139+
return Task::perform(docker::stop_then_list(name), |result| {
140+
cosmic::Action::App(Message::ContainersLoaded(result))
141+
});
76142
}
77143
}
78144
Task::none()
79145
}
80146

81147
fn view(&self) -> Element<'_, Self::Message> {
82-
widget::button::text(PANEL_EMOJI)
148+
widget::button::text(self.panel_label())
83149
.padding(4)
84150
.on_press(Message::TogglePopup)
85151
.into()
@@ -95,12 +161,53 @@ impl cosmic::Application for App {
95161
}
96162

97163
fn popup_view(state: &App) -> Element<'_, cosmic::Action<Message>> {
98-
let content = widget::column![
99-
widget::text(PANEL_EMOJI).size(28),
100-
widget::text(POPUP_GREETING),
101-
]
102-
.spacing(8)
103-
.padding(12);
104-
105-
Element::from(state.core.applet.popup_container(content)).map(cosmic::Action::App)
164+
let mut content = widget::column![].spacing(6).padding(12);
165+
166+
content = content.push(
167+
widget::row![
168+
widget::text("Containers").size(18),
169+
widget::button::text("Refresh").on_press(Message::Refresh),
170+
]
171+
.align_y(Alignment::Center)
172+
.spacing(12),
173+
);
174+
175+
if state.loading {
176+
content = content.push(widget::text("Loading..."));
177+
}
178+
179+
if let Some(err) = &state.error {
180+
content = content.push(widget::text(err).size(12));
181+
}
182+
183+
if state.containers.is_empty() && !state.loading && state.error.is_none() {
184+
content = content.push(widget::text("No containers."));
185+
}
186+
187+
for container in &state.containers {
188+
let name = container.name.clone();
189+
let line = format!("{} — {}", container.name, container.state);
190+
let row = if container.is_running() {
191+
widget::row![
192+
widget::text(line).width(Length::FillPortion(3)),
193+
widget::button::text("Stop").on_press(Message::Stop(name)),
194+
]
195+
} else {
196+
widget::row![
197+
widget::text(line).width(Length::FillPortion(3)),
198+
widget::button::text("Play").on_press(Message::Start(name)),
199+
]
200+
};
201+
content = content.push(row.align_y(Alignment::Center).spacing(8));
202+
}
203+
204+
Element::from(
205+
state
206+
.core
207+
.applet
208+
.popup_container(content)
209+
.max_width(360.0)
210+
.max_height(420.0),
211+
)
212+
.map(cosmic::Action::App)
106213
}

0 commit comments

Comments
 (0)