@@ -16,44 +16,33 @@ use objc2_foundation::{
1616
1717use self :: workspace_button:: WorkspaceButton ;
1818use self :: workspaces_stack_view:: WorkspacesStackView ;
19+ use crate :: config:: Config ;
1920use crate :: macos:: layout_button:: LayoutButton ;
21+ use crate :: macos:: windows:: settings:: SettingsWindowController ;
2022
2123mod layout_button;
24+ mod windows;
2225mod workspace_button;
2326mod workspaces_stack_view;
2427
25- #[ derive( Debug ) ]
28+ #[ derive( Default ) ]
2629pub struct AppDelegateIvars {
2730 ns_status_item : OnceCell < Retained < NSStatusItem > > ,
2831 ns_stack_view : OnceCell < Retained < WorkspacesStackView > > ,
2932 buttons : RefCell < Vec < Retained < NSView > > > ,
30- }
31-
32- impl Default for AppDelegateIvars {
33- fn default ( ) -> Self {
34- Self {
35- ns_status_item : OnceCell :: new ( ) ,
36- ns_stack_view : OnceCell :: new ( ) ,
37- buttons : RefCell :: new ( Vec :: new ( ) ) ,
38- }
39- }
33+ config : OnceCell < Config > ,
34+ settings_window : OnceCell < Retained < windows:: settings:: SettingsWindowController > > ,
4035}
4136
4237define_class ! (
43- // SAFETY:
44- // - The superclass NSObject does not have any subclassing requirements.
45- // - `Delegate` does not implement `Drop`.
4638 #[ unsafe ( super = NSObject ) ]
4739 #[ thread_kind = MainThreadOnly ]
4840 #[ ivars = AppDelegateIvars ]
4941 pub struct AppDelegate ;
5042
51- // SAFETY: `NSObjectProtocol` has no safety requirements.
5243 unsafe impl NSObjectProtocol for AppDelegate { }
5344
54- // SAFETY: `NSApplicationDelegate` has no safety requirements.
5545 unsafe impl NSApplicationDelegate for AppDelegate {
56- // SAFETY: The signature is correct.
5746 #[ unsafe ( method( applicationDidFinishLaunching: ) ) ]
5847 fn did_finish_launching( & self , notification: & NSNotification ) {
5948 let mtm = self . mtm( ) ;
@@ -64,41 +53,36 @@ define_class!(
6453 . downcast:: <NSApplication >( )
6554 . unwrap( ) ;
6655
67- // Set the activation policy to Accessory to hide the dock icon and menu bar.
6856 app. setActivationPolicy( NSApplicationActivationPolicy :: Accessory ) ;
6957
70- // Activate the application.
71- // Required when launching unbundled (as is done with Cargo).
7258 #[ allow( deprecated) ]
7359 #[ cfg( debug_assertions) ]
7460 app. activateIgnoringOtherApps( true ) ;
7561
7662 let komorebi_state = crate :: komorebi:: read_state( ) . unwrap_or_default( ) ;
7763
78- // create status bar item
7964 let ns_status_bar = NSStatusBar :: systemStatusBar( ) ;
8065 let ns_status_item = ns_status_bar. statusItemWithLength( NSVariableStatusItemLength ) ;
8166
82- // Create stack view for horizontal button layout
8367 let stack_view = {
8468 let stack = WorkspacesStackView :: new( mtm) ;
8569 stack. setOrientation( NSUserInterfaceLayoutOrientation :: Horizontal ) ;
8670 stack. setSpacing( 2.0 ) ;
8771 stack
8872 } ;
8973
90- // Add stack view to status item button
9174 if let Some ( btn) = ns_status_item. button( mtm) {
9275 btn. addSubview( & stack_view) ;
9376 }
9477
9578 let _ = self . ivars( ) . ns_status_item. set( ns_status_item) ;
9679 let _ = self . ivars( ) . ns_stack_view. set( stack_view) ;
9780
98- // Create initial workspace buttons
81+ let config = Config :: load( ) . unwrap_or_default( ) ;
82+ let _ = self . ivars( ) . config. set( config) ;
83+
9984 self . update_workspace_buttons( komorebi_state) ;
10085
101- // Listen for komorebi state changes on a separate thread
10286 std:: thread:: spawn( || {
10387 crate :: komorebi:: listen_for_state( |new_state| {
10488 Queue :: main( ) . exec_async( || AppDelegate :: dispatch_new_state( new_state) ) ;
@@ -111,68 +95,69 @@ define_class!(
11195impl AppDelegate {
11296 pub fn new ( mtm : MainThreadMarker ) -> Retained < Self > {
11397 let this = Self :: alloc ( mtm) . set_ivars ( AppDelegateIvars :: default ( ) ) ;
114- // SAFETY: The signature of `NSObject`'s `init` method is correct.
11598 unsafe { msg_send ! [ super ( this) , init] }
11699 }
117100
118101 fn dispatch_new_state ( state : crate :: komorebi:: State ) {
119- // SAFETY: This is called on the main thread using `Queue::main()`.
120102 let mtm = MainThreadMarker :: new ( ) . unwrap ( ) ;
121103 let app = NSApp ( mtm) ;
122- // SAFETY: We have set a delegate for the application.
123104 let delegate = app. delegate ( ) . unwrap ( ) ;
124105 if let Ok ( delegate) = delegate. downcast :: < Self > ( ) {
125106 delegate. update_workspace_buttons ( state) ;
126107 }
127108 }
128109
110+ fn show_or_create_settings_window ( & self ) {
111+ if let Some ( existing) = self . ivars ( ) . settings_window . get ( ) {
112+ existing. show ( ) ;
113+ return ;
114+ }
115+
116+ let mtm = self . mtm ( ) ;
117+ let config = self . ivars ( ) . config . get ( ) . unwrap ( ) . clone ( ) ;
118+
119+ let window_controller = SettingsWindowController :: new ( mtm, config) ;
120+
121+ let _ = self . ivars ( ) . settings_window . set ( window_controller) ;
122+ self . ivars ( ) . settings_window . get ( ) . unwrap ( ) . show ( ) ;
123+ }
124+
129125 fn update_workspace_buttons ( & self , state : crate :: komorebi:: State ) {
130126 let mtm = self . mtm ( ) ;
131- // SAFETY: We have initialized these ivars in `did_finish_launching`.
132127 let stack_view = self . ivars ( ) . ns_stack_view . get ( ) . unwrap ( ) ;
133128 let mut views = self . ivars ( ) . buttons . borrow_mut ( ) ;
129+ let config = self . ivars ( ) . config . get ( ) . unwrap ( ) ;
134130
135- // Remove all existing buttons from stack view
136131 for button in views. iter ( ) {
137132 button. removeFromSuperview ( ) ;
138133 }
139134
140135 views. clear ( ) ;
141136
142- // Get first monitor (we only support one for now)
143137 let Some ( monitor) = state. monitors . first ( ) else {
144138 return ;
145139 } ;
146140
147- // Create new buttons for all workspaces
148141 for workspace in & monitor. workspaces {
149142 let workspace_button = WorkspaceButton :: new ( mtm, workspace) ;
150143 stack_view. addArrangedSubview ( & workspace_button) ;
151-
152- // Store button
153144 views. push ( workspace_button. downcast ( ) . unwrap ( ) ) ;
154145 }
155146
156- // show layout button for focused workspace
157- if let Some ( focused_ws) = monitor. focused_workspace ( ) {
158- let separator = NSTextField :: labelWithString ( ns_string ! ( "|" ) , mtm) ;
159- separator. setAlignment ( NSTextAlignment :: Center ) ;
160- stack_view. addArrangedSubview ( & separator) ;
147+ if config. show_layout_button {
148+ if let Some ( focused_ws) = monitor. focused_workspace ( ) {
149+ let separator = NSTextField :: labelWithString ( ns_string ! ( "|" ) , mtm) ;
150+ separator. setAlignment ( NSTextAlignment :: Center ) ;
151+ stack_view. addArrangedSubview ( & separator) ;
152+ views. push ( separator. downcast ( ) . unwrap ( ) ) ;
161153
162- // Store separator
163- views. push ( separator. downcast ( ) . unwrap ( ) ) ;
164-
165- let layout_button = LayoutButton :: new ( mtm, focused_ws) ;
166- stack_view. addArrangedSubview ( & layout_button) ;
167-
168- // Store button
169- views. push ( layout_button. downcast ( ) . unwrap ( ) ) ;
154+ let layout_button = LayoutButton :: new ( mtm, focused_ws) ;
155+ stack_view. addArrangedSubview ( & layout_button) ;
156+ views. push ( layout_button. downcast ( ) . unwrap ( ) ) ;
157+ }
170158 }
171159
172- // SAFETY: We have initialized this ivar in `did_finish_launching`.
173160 let ns_status_item = self . ivars ( ) . ns_status_item . get ( ) . unwrap ( ) ;
174-
175- // Update status item button frame to match new stack view size
176161 if let Some ( btn) = ns_status_item. button ( mtm) {
177162 let fitting_size = stack_view. fittingSize ( ) ;
178163 let size = NSSize :: new ( fitting_size. width , WorkspaceButton :: HEIGHT ) ;
@@ -184,7 +169,6 @@ impl AppDelegate {
184169}
185170
186171pub fn run ( ) -> anyhow:: Result < ( ) > {
187- // SAFETY: `run` is the main entry point and is called on the main thread.
188172 let mtm = MainThreadMarker :: new ( ) . unwrap ( ) ;
189173
190174 let app = NSApplication :: sharedApplication ( mtm) ;
0 commit comments