Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ label.network-poor { color: var(--error-color); }

/* Loading spinner */
.loading-spinner {
color: var(--text-secondary);
margin-top: 12px;
margin-bottom: 12px;
opacity: 0.6;
Expand Down Expand Up @@ -454,4 +455,3 @@ popover row label {
color: var(--text-secondary);
font-size: 13px;
}

2 changes: 1 addition & 1 deletion src/themes/catppuccin.css
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ label.network-poor { color: var(--error-color); }
.wifi-open { color: var(--text-primary); }

.loading-spinner {
color: var(--text-secondary);
margin-top: 12px;
margin-bottom: 12px;
opacity: 0.6;
Expand Down Expand Up @@ -361,4 +362,3 @@ popover row label {
background: var(--bg-secondary);
border-color: var(--border-color-hover);
}

2 changes: 1 addition & 1 deletion src/themes/dracula.css
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ label.network-poor { color: var(--error-color); }
.wifi-open { color: var(--text-primary); }

.loading-spinner {
color: var(--text-secondary);
margin-top: 12px;
margin-bottom: 12px;
opacity: 0.6;
Expand Down Expand Up @@ -361,4 +362,3 @@ popover row label {
background: var(--bg-secondary);
border-color: var(--border-color-hover);
}

2 changes: 1 addition & 1 deletion src/themes/gruvbox.css
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ label.network-poor { color: var(--error-color); }
.wifi-open { color: var(--text-primary); }

.loading-spinner {
color: var(--text-secondary);
margin-top: 12px;
margin-bottom: 12px;
opacity: 0.6;
Expand Down Expand Up @@ -392,4 +393,3 @@ popover row label {
background: var(--bg-secondary);
border-color: var(--border-color-hover);
}

2 changes: 1 addition & 1 deletion src/themes/nord.css
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ label.network-poor { color: var(--error-color); }
.wifi-open { color: var(--text-primary); }

.loading-spinner {
color: var(--text-secondary);
margin-top: 12px;
margin-bottom: 12px;
opacity: 0.6;
Expand Down Expand Up @@ -361,4 +362,3 @@ popover row label {
background: var(--bg-secondary);
border-color: var(--border-color-hover);
}

2 changes: 1 addition & 1 deletion src/themes/tokyo.css
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ label.network-poor { color: var(--error-color); }
.wifi-open { color: var(--text-primary); }

.loading-spinner {
color: var(--text-secondary);
margin-top: 12px;
margin-bottom: 12px;
opacity: 0.6;
Expand Down Expand Up @@ -361,4 +362,3 @@ popover row label {
background: var(--bg-secondary);
border-color: var(--border-color-hover);
}

4 changes: 4 additions & 0 deletions src/ui/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use nmrs::{
};
use std::rc::Rc;

use crate::ui::inherit_color_scheme;

pub fn connect_modal(
nm: Rc<NetworkManager>,
parent: &ApplicationWindow,
Expand Down Expand Up @@ -45,6 +47,7 @@ fn draw_connect_modal(
dialog.set_title(Some("Connect to Network"));
dialog.set_transient_for(Some(parent));
dialog.set_modal(true);
inherit_color_scheme(&dialog, parent);
dialog.add_css_class("diag-buttons");

let content_area = dialog.content_area();
Expand Down Expand Up @@ -124,6 +127,7 @@ fn draw_connect_modal(
("Open", ResponseType::Accept),
],
);
inherit_color_scheme(&file_dialog, &parent_dialog);

let cert_entry = cert_entry_for_browse.clone();
file_dialog.connect_response(move |dialog, response| {
Expand Down
101 changes: 100 additions & 1 deletion src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,89 @@ use tokio::sync::Notify;
type Callback = Rc<dyn Fn()>;
type CallbackCell = Rc<std::cell::RefCell<Option<Callback>>>;

const COLOR_SCHEME_PROPERTY: &str = "gtk-interface-color-scheme";

// Read the GTK 4.20 property dynamically for compatibility with older releases
fn system_prefers_dark(settings: &gtk::Settings) -> Option<bool> {
if !settings.has_property(COLOR_SCHEME_PROPERTY) {
return None;
}

// Treat unknown values as GTK's default light scheme
glib::EnumValue::from_value(&settings.property_value(COLOR_SCHEME_PROPERTY)).and_then(
|(_, value)| match value.nick() {
"unsupported" => None,
"dark" => Some(true),
_ => Some(false),
},
)
}

fn system_color_scheme() -> Option<(gtk::Settings, bool)> {
let settings = gtk::Settings::default()?;
system_prefers_dark(&settings).map(|prefers_dark| (settings, prefers_dark))
}

fn set_interface_color_scheme(settings: &gtk::Settings, prefers_dark: bool) {
settings.set_gtk_application_prefer_dark_theme(prefers_dark);

let Some(property) = settings.find_property(COLOR_SCHEME_PROPERTY) else {
return;
};
let Some(value) = glib::EnumClass::with_type(property.value_type())
.and_then(|class| class.to_value_by_nick(if prefers_dark { "dark" } else { "light" }))
else {
return;
};

settings.set_property_from_value(COLOR_SCHEME_PROPERTY, &value);
}

fn update_color_scheme(window: &impl IsA<gtk::Widget>, prefers_dark: bool) {
window.remove_css_class("dark-theme");
window.remove_css_class("light-theme");
window.add_css_class(if prefers_dark {
"dark-theme"
} else {
"light-theme"
});
}

pub(crate) fn inherit_color_scheme(window: &impl IsA<gtk::Widget>, parent: &impl IsA<gtk::Widget>) {
update_color_scheme(window, parent.has_css_class("dark-theme"));
}

fn sync_system_color_scheme(window: &ApplicationWindow) {
let Some((settings, prefers_dark)) = system_color_scheme() else {
return;
};

settings.set_gtk_application_prefer_dark_theme(prefers_dark);
update_color_scheme(window, prefers_dark);
}

pub(crate) fn supports_system_color_scheme() -> bool {
system_color_scheme().is_some()
}

pub(crate) fn apply_color_scheme_override(window: &ApplicationWindow, prefers_dark: bool) {
if let Some(settings) = gtk::Settings::default() {
set_interface_color_scheme(&settings, prefers_dark);
}

update_color_scheme(window, prefers_dark);
}

pub(crate) fn apply_system_color_scheme(window: &ApplicationWindow) {
if let Some(settings) = gtk::Settings::default()
&& settings.has_property(COLOR_SCHEME_PROPERTY)
{
settings.reset_property(COLOR_SCHEME_PROPERTY);
}

sync_system_color_scheme(window);
}

pub fn freq_to_band(freq: u32) -> Option<&'static str> {
match freq {
2400..=2500 => Some("2.4GHz"),
Expand All @@ -35,7 +118,23 @@ pub fn build_ui(app: &Application) {
let win = ApplicationWindow::new(app);
win.set_title(Some(""));
win.set_default_size(450, 600);
win.add_css_class("dark-theme");

// Preserve the dark default when system preferences are unavailable
if let Some((settings, prefers_dark)) = system_color_scheme() {
win.add_css_class("system-theme");
update_color_scheme(&win, prefers_dark);

let win_weak = win.downgrade();
settings.connect_notify_local(Some(COLOR_SCHEME_PROPERTY), move |_, _| {
if let Some(window) = win_weak.upgrade()
&& window.has_css_class("system-theme")
{
sync_system_color_scheme(&window);
}
});
} else {
apply_color_scheme_override(&win, true);
}

let vbox = GtkBox::new(Orientation::Vertical, 0);
let status = Label::new(None);
Expand Down
1 change: 1 addition & 0 deletions src/ui/network_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl NetworkPage {

let header = Box::new(Orientation::Horizontal, 6);
let icon = Image::from_icon_name("network-wireless-signal-excellent-symbolic");
icon.add_css_class("network-icon");
icon.set_pixel_size(24);

let title = Label::new(None);
Expand Down
43 changes: 36 additions & 7 deletions src/ui/settings_page.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use gtk::prelude::*;
use gtk::{Align, Box, Button, Label, Orientation};

use crate::ui::apply_color_scheme_override;
use crate::ui::apply_system_color_scheme;
use crate::ui::header::THEMES;
use crate::ui::supports_system_color_scheme;

const CUSTOM_INDEX: u32 = 0;

Expand Down Expand Up @@ -36,7 +39,7 @@ impl SettingsPage {
root.append(&title);

Self::build_theme_section(&root);
Self::build_light_dark_section(&root, window);
Self::build_appearance_section(&root, window);

Self { root }
}
Expand Down Expand Up @@ -96,7 +99,7 @@ impl SettingsPage {
root.append(&section);
}

fn build_light_dark_section(root: &gtk::Box, window: &gtk::ApplicationWindow) {
fn build_appearance_section(root: &gtk::Box, window: &gtk::ApplicationWindow) {
let section = Box::new(Orientation::Vertical, 6);

let label = Label::new(Some("Appearance"));
Expand All @@ -106,6 +109,10 @@ impl SettingsPage {

let toggle_box = Box::new(Orientation::Horizontal, 8);

let system_btn = Button::with_label("System");
system_btn.add_css_class("appearance-btn");
system_btn.set_visible(supports_system_color_scheme());

let light_btn = Button::with_label("Light");
light_btn.add_css_class("appearance-btn");

Expand All @@ -114,36 +121,58 @@ impl SettingsPage {

{
let window_weak = window.downgrade();
let light_btn_clone = light_btn.clone();
let dark_btn_clone = dark_btn.clone();
system_btn.connect_clicked(move |btn| {
if let Some(window) = window_weak.upgrade() {
window.add_css_class("system-theme");
apply_system_color_scheme(&window);
btn.add_css_class("appearance-active");
light_btn_clone.remove_css_class("appearance-active");
dark_btn_clone.remove_css_class("appearance-active");
}
});
}

{
let window_weak = window.downgrade();
let system_btn_clone = system_btn.clone();
let dark_btn_clone = dark_btn.clone();
light_btn.connect_clicked(move |btn| {
if let Some(window) = window_weak.upgrade() {
window.remove_css_class("dark-theme");
window.add_css_class("light-theme");
window.remove_css_class("system-theme");
apply_color_scheme_override(&window, false);
btn.add_css_class("appearance-active");
system_btn_clone.remove_css_class("appearance-active");
dark_btn_clone.remove_css_class("appearance-active");
}
});
}

{
let window_weak = window.downgrade();
let system_btn_clone = system_btn.clone();
let light_btn_clone = light_btn.clone();
dark_btn.connect_clicked(move |btn| {
if let Some(window) = window_weak.upgrade() {
window.remove_css_class("light-theme");
window.add_css_class("dark-theme");
window.remove_css_class("system-theme");
apply_color_scheme_override(&window, true);
btn.add_css_class("appearance-active");
system_btn_clone.remove_css_class("appearance-active");
light_btn_clone.remove_css_class("appearance-active");
}
});
}

if window.has_css_class("light-theme") {
if window.has_css_class("system-theme") {
system_btn.add_css_class("appearance-active");
} else if window.has_css_class("light-theme") {
light_btn.add_css_class("appearance-active");
} else {
dark_btn.add_css_class("appearance-active");
}

toggle_box.append(&system_btn);
toggle_box.append(&light_btn);
toggle_box.append(&dark_btn);
section.append(&toggle_box);
Expand Down
3 changes: 3 additions & 0 deletions src/ui/vpn_add_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use nmrs::{NetworkManager, WireGuardConfig, WireGuardPeer};
use std::cell::RefCell;
use std::rc::Rc;

use crate::ui::inherit_color_scheme;

type OnSuccessCallback = Rc<RefCell<Option<Rc<dyn Fn()>>>>;

pub struct VpnAddPage {
Expand Down Expand Up @@ -293,6 +295,7 @@ impl VpnAddPage {
("Open", ResponseType::Accept),
],
);
inherit_color_scheme(&dialog, &parent);
dialog.connect_response(move |dialog, response| {
if response == ResponseType::Accept
&& let Some(file) = dialog.file()
Expand Down
1 change: 1 addition & 0 deletions src/ui/wired_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ impl WiredPage {

let header = Box::new(Orientation::Horizontal, 6);
let icon = Image::from_icon_name("network-wired-symbolic");
icon.add_css_class("wired-device-icon");
icon.set_pixel_size(24);

let title = Label::new(None);
Expand Down