A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1use crate::state::AppState;
2use crate::ui::window::{self, RbApplicationWindow};
3use crate::{app, ui::media_controls};
4use adw::prelude::*;
5use adw::subclass::prelude::*;
6use gio::subclass::prelude::ApplicationImpl;
7use glib::subclass::types::ObjectSubclass;
8use glib::WeakRef;
9use gtk::{gdk::Display, gio, glib, CssProvider, STYLE_PROVIDER_PRIORITY_APPLICATION};
10use std::cell::OnceCell;
11
12mod imp {
13
14 use super::*;
15 pub struct RbApplication {
16 pub state: OnceCell<AppState>,
17 pub window: OnceCell<WeakRef<RbApplicationWindow>>,
18 }
19
20 #[glib::object_subclass]
21 impl ObjectSubclass for RbApplication {
22 const NAME: &'static str = "RockboxApplication";
23 type Type = app::RbApplication;
24 type ParentType = adw::Application;
25
26 fn new() -> Self {
27 Self {
28 state: OnceCell::new(),
29 window: OnceCell::new(),
30 }
31 }
32 }
33
34 impl ObjectImpl for RbApplication {}
35
36 impl ApplicationImpl for RbApplication {
37 fn activate(&self) {
38 let app = self.obj();
39 load_css();
40
41 if let Some(weak_window) = self.window.get() {
42 weak_window.upgrade().unwrap().present();
43 return;
44 }
45
46 let window = app.create_window();
47 let _ = self.window.set(window.downgrade());
48
49 app.setup_gactions();
50 }
51
52 fn shutdown(&self) {
53 self.parent_shutdown();
54 }
55 }
56
57 impl GtkApplicationImpl for RbApplication {}
58 impl AdwApplicationImpl for RbApplication {}
59}
60
61glib::wrapper! {
62 pub struct RbApplication(ObjectSubclass<imp::RbApplication>)
63 @extends gio::Application, gtk::Application, adw::Application,
64 @implements gio::ActionMap, gio::ActionGroup;
65}
66
67fn load_css() {
68 let provider = CssProvider::new();
69 provider.load_from_string(include_str!("styles.css"));
70 gtk::style_context_add_provider_for_display(
71 &Display::default().expect("Error initializing gtk css provider."),
72 &provider,
73 STYLE_PROVIDER_PRIORITY_APPLICATION,
74 );
75}
76
77impl RbApplication {
78 pub fn run() -> glib::ExitCode {
79 // Create new GObject and downcast it into RbApplication
80 let app = glib::Object::builder::<RbApplication>()
81 .property("application-id", Some("io.github.tsirysndr.Rockbox"))
82 .property("flags", gio::ApplicationFlags::empty())
83 .property("resource-base-path", Some("/io/github/tsirysndr/Rockbox"))
84 .build();
85
86 // Start running gtk::Application
87 app.run()
88 }
89
90 pub fn create_window(&self) -> RbApplicationWindow {
91 let app_state = self.imp().state.get_or_init(AppState::new);
92 let window = RbApplicationWindow::new(app_state.clone());
93
94 self.add_window(&window);
95
96 window.present();
97 window
98 }
99
100 pub fn setup_gactions(&self) {
101 let window = self.imp().window.get();
102 let window = window.unwrap().upgrade().unwrap();
103
104 let media_controls = window.imp().media_control_bar.get();
105 let like = gio::ActionEntry::builder("like")
106 .activate(move |_, _, _| {
107 media_controls.like();
108 })
109 .build();
110
111 let media_controls = window.imp().media_control_bar.get();
112 let play = gio::ActionEntry::builder("play_pause")
113 .activate(move |_, _, _| {
114 media_controls.play();
115 })
116 .build();
117
118 let media_controls = window.imp().media_control_bar.get();
119 let previous = gio::ActionEntry::builder("previous")
120 .activate(move |_, _, _| {
121 media_controls.previous();
122 })
123 .build();
124
125 let media_controls = window.imp().media_control_bar.get();
126 let next = gio::ActionEntry::builder("next")
127 .activate(move |_, _, _| {
128 media_controls.next();
129 })
130 .build();
131
132 let media_controls = window.imp().media_control_bar.get();
133 let shuffle = gio::ActionEntry::builder("shuffle")
134 .activate(move |_, _, _| {
135 media_controls.shuffle();
136 })
137 .build();
138
139 let window_clone = window.clone();
140 let refresh_library = gio::ActionEntry::builder("refresh_library")
141 .activate(move |_, _, _| {
142 window_clone.imp().refresh_library();
143 })
144 .build();
145
146 let media_controls = window.imp().media_control_bar.get();
147 let seek_backward = gio::ActionEntry::builder("seek_backward")
148 .activate(move |_, _, _| {
149 media_controls.seek_backward();
150 })
151 .build();
152
153 let media_controls = window.imp().media_control_bar.get();
154 let seek_forward = gio::ActionEntry::builder("seek_forward")
155 .activate(move |_, _, _| {
156 media_controls.seek_forward();
157 })
158 .build();
159
160 self.add_action_entries([
161 like,
162 play,
163 previous,
164 next,
165 shuffle,
166 refresh_library,
167 seek_backward,
168 seek_forward,
169 ]);
170
171 self.set_accels_for_action("win.toggle_search", &["<primary>f"]);
172 self.set_accels_for_action("app.preferences", &["<primary>comma"]);
173 self.set_accels_for_action("app.quit", &["<primary>q"]);
174
175 self.set_accels_for_action("app.refresh_library", &["<primary>r"]);
176 self.set_accels_for_action("app.like", &["<primary>l"]);
177
178 self.set_accels_for_action("app.play_pause", &["<primary>space"]);
179 self.set_accels_for_action("app.previous", &["<primary>Left"]);
180 self.set_accels_for_action("app.next", &["<primary>Right"]);
181 self.set_accels_for_action("app.seek_backward", &["<Shift><primary>Left"]);
182 self.set_accels_for_action("app.seek_forward", &["<Shift><primary>Right"]);
183 self.set_accels_for_action("app.shuffle", &["<primary>s"]);
184 }
185}
186
187impl Default for RbApplication {
188 fn default() -> Self {
189 gio::Application::default()
190 .expect("Could not get default GApplication")
191 .downcast()
192 .unwrap()
193 }
194}