discourse-systray 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/discourse_systray/systray.rb +456 -155
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2a747e5c405b575a86d5ec8976fcd670f089b5af9bd5452b6077138c8a63830
|
4
|
+
data.tar.gz: 1a2354eece536b24f535b0bdd46612517685da8402703d07da40f136ad33d63c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e8cd660630fdd77c5b9ba901caa3507593e87524dd12b884a7fffd59c1070c1d065bed0297694c806841a463ad3d4cf41d660b3a92d70f186fba4494cfc002c
|
7
|
+
data.tar.gz: 5f3b2075cda0f0a8b7703fb34f06c075357325d16bb9964a5c7c21d3350a697b45286b7764964aa46a84d3db37a22ee8c22ab553170e125095d7ee1808d6c6d8
|
@@ -74,19 +74,73 @@ module ::DiscourseSystray
|
|
74
74
|
rescue JSON::ParserError
|
75
75
|
{}
|
76
76
|
end
|
77
|
-
BUFFER_SIZE =
|
77
|
+
BUFFER_SIZE = 1000
|
78
|
+
BUFFER_TRIM_INTERVAL = 30 # seconds
|
78
79
|
|
79
80
|
def initialize
|
81
|
+
puts "DEBUG: Initializing DiscourseSystray" if OPTIONS[:debug]
|
82
|
+
|
80
83
|
@discourse_path = self.class.load_or_prompt_config unless OPTIONS[:attach]
|
84
|
+
puts "DEBUG: Discourse path: #{@discourse_path}" if OPTIONS[:debug]
|
85
|
+
|
81
86
|
@running = false
|
82
87
|
@ember_output = []
|
83
88
|
@unicorn_output = []
|
84
89
|
@processes = {}
|
85
90
|
@ember_running = false
|
86
91
|
@unicorn_running = false
|
87
|
-
@ember_line_count = 0
|
88
|
-
@unicorn_line_count = 0
|
89
92
|
@status_window = nil
|
93
|
+
@buffer_trim_timer = nil
|
94
|
+
|
95
|
+
# Initialize pipe queue for background processing
|
96
|
+
initialize_pipe_queue
|
97
|
+
|
98
|
+
# Add initial welcome message to buffers with timestamp
|
99
|
+
timestamp = Time.now.strftime("%H:%M:%S")
|
100
|
+
@ember_output << "#{timestamp} - Discourse Ember CLI Log\n"
|
101
|
+
@ember_output << "Start Discourse to see Ember CLI logs here.\n"
|
102
|
+
@ember_output << "\n"
|
103
|
+
|
104
|
+
@unicorn_output << "#{timestamp} - Discourse Unicorn Log\n"
|
105
|
+
@unicorn_output << "Start Discourse to see Unicorn logs here.\n"
|
106
|
+
@unicorn_output << "\n"
|
107
|
+
|
108
|
+
# Add a visual separator
|
109
|
+
@ember_output << "=" * 50 + "\n"
|
110
|
+
@unicorn_output << "=" * 50 + "\n"
|
111
|
+
|
112
|
+
puts "DEBUG: Added initial data to buffers" if OPTIONS[:debug]
|
113
|
+
puts "DEBUG: ember_output size: #{@ember_output.size}" if OPTIONS[:debug]
|
114
|
+
puts "DEBUG: unicorn_output size: #{@unicorn_output.size}" if OPTIONS[:debug]
|
115
|
+
|
116
|
+
# Set up periodic buffer trimming
|
117
|
+
setup_buffer_trim_timer unless OPTIONS[:attach]
|
118
|
+
|
119
|
+
puts "DEBUG: Initialized DiscourseSystray with path: #{@discourse_path}" if OPTIONS[:debug]
|
120
|
+
end
|
121
|
+
|
122
|
+
def setup_buffer_trim_timer
|
123
|
+
@buffer_trim_timer = GLib::Timeout.add_seconds(BUFFER_TRIM_INTERVAL) do
|
124
|
+
trim_buffers
|
125
|
+
true # Keep the timer running
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def trim_buffers
|
130
|
+
# Trim buffers if they exceed the buffer size
|
131
|
+
if @ember_output.size > BUFFER_SIZE
|
132
|
+
excess = @ember_output.size - BUFFER_SIZE
|
133
|
+
@ember_output.shift(excess)
|
134
|
+
@ember_line_count = [@ember_line_count - excess, 0].max
|
135
|
+
end
|
136
|
+
|
137
|
+
if @unicorn_output.size > BUFFER_SIZE
|
138
|
+
excess = @unicorn_output.size - BUFFER_SIZE
|
139
|
+
@unicorn_output.shift(excess)
|
140
|
+
@unicorn_line_count = [@unicorn_line_count - excess, 0].max
|
141
|
+
end
|
142
|
+
|
143
|
+
true
|
90
144
|
end
|
91
145
|
|
92
146
|
def init_systray
|
@@ -182,6 +236,27 @@ module ::DiscourseSystray
|
|
182
236
|
end
|
183
237
|
end
|
184
238
|
@view_timeouts&.clear
|
239
|
+
|
240
|
+
# Remove buffer trim timer if it exists
|
241
|
+
if @buffer_trim_timer
|
242
|
+
begin
|
243
|
+
GLib::Source.remove(@buffer_trim_timer)
|
244
|
+
@buffer_trim_timer = nil
|
245
|
+
rescue StandardError => e
|
246
|
+
puts "Error removing buffer trim timer: #{e}" if OPTIONS[:debug]
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Stop pipe thread
|
251
|
+
if @pipe_thread
|
252
|
+
begin
|
253
|
+
@pipe_queue.push(:exit) if @pipe_queue
|
254
|
+
@pipe_thread.join(2) # Wait up to 2 seconds
|
255
|
+
@pipe_thread.kill if @pipe_thread.alive?
|
256
|
+
rescue StandardError => e
|
257
|
+
puts "Error stopping pipe thread: #{e}" if OPTIONS[:debug]
|
258
|
+
end
|
259
|
+
end
|
185
260
|
|
186
261
|
# Then stop processes
|
187
262
|
@processes.each do |name, process|
|
@@ -207,31 +282,91 @@ module ::DiscourseSystray
|
|
207
282
|
end
|
208
283
|
|
209
284
|
def start_process(command, console: false)
|
285
|
+
puts "DEBUG: start_process called with command: #{command}" if OPTIONS[:debug]
|
286
|
+
|
210
287
|
return start_console_process(command) if console
|
211
|
-
|
288
|
+
|
289
|
+
begin
|
290
|
+
stdin, stdout, stderr, wait_thr = Open3.popen3(command)
|
291
|
+
puts "DEBUG: Process started with PID: #{wait_thr.pid}" if OPTIONS[:debug]
|
292
|
+
rescue => e
|
293
|
+
puts "DEBUG: Error starting process: #{e.message}" if OPTIONS[:debug]
|
294
|
+
return nil
|
295
|
+
end
|
212
296
|
|
213
297
|
# Create a monitor thread that will detect if process dies
|
214
298
|
monitor_thread =
|
215
299
|
Thread.new do
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
300
|
+
begin
|
301
|
+
wait_thr.value # Wait for process to finish
|
302
|
+
is_ember = command.include?("ember-cli")
|
303
|
+
@ember_running = false if is_ember
|
304
|
+
@unicorn_running = false unless is_ember
|
305
|
+
GLib::Idle.add do
|
306
|
+
update_tab_labels if @notebook
|
307
|
+
false
|
308
|
+
end
|
309
|
+
rescue => e
|
310
|
+
puts "DEBUG: Error in monitor thread: #{e.message}" if OPTIONS[:debug]
|
223
311
|
end
|
224
312
|
end
|
225
313
|
|
226
|
-
#
|
314
|
+
# Clear the buffer before starting
|
315
|
+
buffer = command.include?("ember-cli") ? @ember_output : @unicorn_output
|
316
|
+
buffer.clear
|
317
|
+
|
318
|
+
# Add a start message to the buffer
|
319
|
+
timestamp = Time.now.strftime("%H:%M:%S")
|
320
|
+
buffer << "#{timestamp} - Starting #{command}...\n"
|
321
|
+
|
322
|
+
# Force immediate GUI update
|
323
|
+
GLib::Idle.add do
|
324
|
+
show_status_window if @status_window.nil? || !@status_window.visible?
|
325
|
+
update_all_views
|
326
|
+
false
|
327
|
+
end
|
328
|
+
|
329
|
+
# Monitor stdout
|
227
330
|
Thread.new do
|
228
|
-
|
229
|
-
|
230
|
-
command.include?("ember-cli") ? @ember_output : @unicorn_output
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
331
|
+
begin
|
332
|
+
while line = stdout.gets
|
333
|
+
buffer = command.include?("ember-cli") ? @ember_output : @unicorn_output
|
334
|
+
puts "[OUT] #{line}" if OPTIONS[:debug]
|
335
|
+
|
336
|
+
# Add to buffer with size management
|
337
|
+
buffer << line
|
338
|
+
|
339
|
+
# Print buffer size for debugging
|
340
|
+
if OPTIONS[:debug]
|
341
|
+
puts "DEBUG: Added to buffer: #{line.inspect}"
|
342
|
+
if buffer.size % 10 == 0
|
343
|
+
puts "DEBUG: Buffer size now: #{buffer.size}"
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# Trim if needed
|
348
|
+
if buffer.size > BUFFER_SIZE
|
349
|
+
buffer.shift(buffer.size - BUFFER_SIZE)
|
350
|
+
end
|
351
|
+
|
352
|
+
# Force GUI update on main thread
|
353
|
+
GLib::Idle.add do
|
354
|
+
update_all_views
|
355
|
+
false
|
356
|
+
end
|
357
|
+
|
358
|
+
# Also publish to pipe for --attach mode in background
|
359
|
+
publish_to_pipe(line)
|
360
|
+
end
|
361
|
+
rescue => e
|
362
|
+
puts "DEBUG: Error in stdout thread: #{e.message}" if OPTIONS[:debug]
|
363
|
+
puts e.backtrace.join("\n") if OPTIONS[:debug]
|
364
|
+
|
365
|
+
# Add error to buffer for visibility
|
366
|
+
buffer = command.include?("ember-cli") ? @ember_output : @unicorn_output
|
367
|
+
error_msg = "ERROR: Exception in stdout thread: #{e.message}\n"
|
368
|
+
buffer << error_msg
|
369
|
+
|
235
370
|
# Force GUI update
|
236
371
|
GLib::Idle.add do
|
237
372
|
update_all_views
|
@@ -240,15 +375,50 @@ module ::DiscourseSystray
|
|
240
375
|
end
|
241
376
|
end
|
242
377
|
|
243
|
-
# Monitor stderr
|
378
|
+
# Monitor stderr
|
244
379
|
Thread.new do
|
245
|
-
|
246
|
-
|
247
|
-
command.include?("ember-cli") ? @ember_output : @unicorn_output
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
380
|
+
begin
|
381
|
+
while line = stderr.gets
|
382
|
+
buffer = command.include?("ember-cli") ? @ember_output : @unicorn_output
|
383
|
+
puts "[ERR] #{line}" if OPTIONS[:debug]
|
384
|
+
|
385
|
+
# Format error line
|
386
|
+
error_line = "ERROR: #{line}"
|
387
|
+
|
388
|
+
# Add to buffer with size management
|
389
|
+
buffer << error_line
|
390
|
+
|
391
|
+
# Print buffer size for debugging
|
392
|
+
if OPTIONS[:debug]
|
393
|
+
puts "DEBUG: Added to buffer: #{error_line.inspect}"
|
394
|
+
if buffer.size % 10 == 0
|
395
|
+
puts "DEBUG: Buffer size now: #{buffer.size}"
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# Trim if needed
|
400
|
+
if buffer.size > BUFFER_SIZE
|
401
|
+
buffer.shift(buffer.size - BUFFER_SIZE)
|
402
|
+
end
|
403
|
+
|
404
|
+
# Force GUI update on main thread
|
405
|
+
GLib::Idle.add do
|
406
|
+
update_all_views
|
407
|
+
false
|
408
|
+
end
|
409
|
+
|
410
|
+
# Also publish to pipe for --attach mode in background
|
411
|
+
publish_to_pipe(error_line)
|
412
|
+
end
|
413
|
+
rescue => e
|
414
|
+
puts "DEBUG: Error in stderr thread: #{e.message}" if OPTIONS[:debug]
|
415
|
+
puts e.backtrace.join("\n") if OPTIONS[:debug]
|
416
|
+
|
417
|
+
# Add error to buffer for visibility
|
418
|
+
buffer = command.include?("ember-cli") ? @ember_output : @unicorn_output
|
419
|
+
error_msg = "ERROR: Exception in stderr thread: #{e.message}\n"
|
420
|
+
buffer << error_msg
|
421
|
+
|
252
422
|
# Force GUI update
|
253
423
|
GLib::Idle.add do
|
254
424
|
update_all_views
|
@@ -268,7 +438,10 @@ module ::DiscourseSystray
|
|
268
438
|
end
|
269
439
|
|
270
440
|
def show_status_window
|
441
|
+
puts "DEBUG: show_status_window called" if OPTIONS[:debug]
|
442
|
+
|
271
443
|
if @status_window&.visible?
|
444
|
+
puts "DEBUG: Status window already visible, presenting it" if OPTIONS[:debug]
|
272
445
|
@status_window.present
|
273
446
|
# Force window to current workspace in i3
|
274
447
|
if @status_window.window
|
@@ -281,15 +454,24 @@ module ::DiscourseSystray
|
|
281
454
|
system("i3-msg '[id=#{@status_window.window.xid}] focus'")
|
282
455
|
end
|
283
456
|
end
|
457
|
+
|
458
|
+
# Force an update of the views even if window is already visible
|
459
|
+
GLib::Idle.add do
|
460
|
+
update_all_views
|
461
|
+
false
|
462
|
+
end
|
463
|
+
|
284
464
|
return
|
285
465
|
end
|
286
466
|
|
287
467
|
# Clean up any existing window
|
288
468
|
if @status_window
|
469
|
+
puts "DEBUG: Destroying existing status window" if OPTIONS[:debug]
|
289
470
|
@status_window.destroy
|
290
471
|
@status_window = nil
|
291
472
|
end
|
292
473
|
|
474
|
+
puts "DEBUG: Creating new status window" if OPTIONS[:debug]
|
293
475
|
@status_window = Gtk::Window.new("Discourse Status")
|
294
476
|
@status_window.set_wmclass("discourse-status", "Discourse Status")
|
295
477
|
|
@@ -299,15 +481,18 @@ module ::DiscourseSystray
|
|
299
481
|
geo = config["window_geometry"]
|
300
482
|
@status_window.move(geo["x"], geo["y"])
|
301
483
|
@status_window.resize(geo["width"], geo["height"])
|
484
|
+
puts "DEBUG: Set window geometry from config: #{geo.inspect}" if OPTIONS[:debug]
|
302
485
|
else
|
303
486
|
@status_window.set_default_size(800, 600)
|
304
487
|
@status_window.window_position = :center
|
488
|
+
puts "DEBUG: Set default window size 800x600" if OPTIONS[:debug]
|
305
489
|
end
|
306
490
|
@status_window.type_hint = :dialog
|
307
491
|
@status_window.set_role("discourse-status-dialog")
|
308
492
|
|
309
493
|
# Handle window destruction and hide
|
310
494
|
@status_window.signal_connect("delete-event") do
|
495
|
+
puts "DEBUG: Window delete-event triggered" if OPTIONS[:debug]
|
311
496
|
save_window_geometry
|
312
497
|
@status_window.hide
|
313
498
|
true # Prevent destruction
|
@@ -319,176 +504,188 @@ module ::DiscourseSystray
|
|
319
504
|
false
|
320
505
|
end
|
321
506
|
|
507
|
+
puts "DEBUG: Creating notebook" if OPTIONS[:debug]
|
322
508
|
@notebook = Gtk::Notebook.new
|
323
509
|
|
510
|
+
# Debug buffer contents before creating views
|
511
|
+
puts "DEBUG: ember_output size: #{@ember_output.size}" if OPTIONS[:debug]
|
512
|
+
puts "DEBUG: unicorn_output size: #{@unicorn_output.size}" if OPTIONS[:debug]
|
513
|
+
|
514
|
+
puts "DEBUG: Creating ember view" if OPTIONS[:debug]
|
324
515
|
@ember_view = create_log_view(@ember_output)
|
325
516
|
@ember_label = create_status_label("Ember CLI", @ember_running)
|
326
517
|
@notebook.append_page(@ember_view, @ember_label)
|
518
|
+
puts "DEBUG: Added ember view to notebook" if OPTIONS[:debug]
|
327
519
|
|
520
|
+
puts "DEBUG: Creating unicorn view" if OPTIONS[:debug]
|
328
521
|
@unicorn_view = create_log_view(@unicorn_output)
|
329
522
|
@unicorn_label = create_status_label("Unicorn", @unicorn_running)
|
330
523
|
@notebook.append_page(@unicorn_view, @unicorn_label)
|
524
|
+
puts "DEBUG: Added unicorn view to notebook" if OPTIONS[:debug]
|
331
525
|
|
332
526
|
@status_window.add(@notebook)
|
527
|
+
puts "DEBUG: Added notebook to status window" if OPTIONS[:debug]
|
528
|
+
|
333
529
|
@status_window.show_all
|
530
|
+
puts "DEBUG: Called show_all on status window" if OPTIONS[:debug]
|
531
|
+
|
532
|
+
# Force an immediate update of the views
|
533
|
+
GLib::Idle.add do
|
534
|
+
puts "DEBUG: Forcing immediate update after window creation" if OPTIONS[:debug]
|
535
|
+
update_all_views
|
536
|
+
false
|
537
|
+
end
|
334
538
|
end
|
335
539
|
|
336
540
|
def update_all_views
|
541
|
+
puts "DEBUG: update_all_views called" if OPTIONS[:debug]
|
542
|
+
|
543
|
+
# Basic validity checks
|
337
544
|
return unless @status_window && !@status_window.destroyed?
|
338
545
|
return unless @ember_view && @unicorn_view
|
339
|
-
|
340
|
-
return if @ember_view.destroyed? || @unicorn_view.destroyed?
|
341
|
-
return if @ember_view.child.destroyed? || @unicorn_view.child.destroyed?
|
342
|
-
|
546
|
+
|
343
547
|
begin
|
344
|
-
|
548
|
+
# Always update both views for now to ensure content is displayed
|
549
|
+
if @ember_view && !@ember_view.destroyed? && @ember_view.child && !@ember_view.child.destroyed?
|
345
550
|
update_log_view(@ember_view.child, @ember_output)
|
346
551
|
end
|
347
|
-
|
552
|
+
|
553
|
+
if @unicorn_view && !@unicorn_view.destroyed? && @unicorn_view.child && !@unicorn_view.child.destroyed?
|
348
554
|
update_log_view(@unicorn_view.child, @unicorn_output)
|
349
555
|
end
|
350
|
-
|
351
|
-
|
556
|
+
|
557
|
+
# Process any pending GTK events
|
558
|
+
while Gtk.events_pending?
|
559
|
+
Gtk.main_iteration_do(false)
|
560
|
+
end
|
561
|
+
rescue => e
|
562
|
+
puts "DEBUG: Error in update_all_views: #{e.message}" if OPTIONS[:debug]
|
563
|
+
puts e.backtrace.join("\n") if OPTIONS[:debug]
|
352
564
|
end
|
353
565
|
end
|
354
566
|
|
355
567
|
def create_log_view(buffer)
|
568
|
+
puts "DEBUG: create_log_view called for #{buffer == @ember_output ? 'ember' : 'unicorn'}" if OPTIONS[:debug]
|
569
|
+
|
570
|
+
# Create a scrolled window to contain the text view
|
356
571
|
scroll = Gtk::ScrolledWindow.new
|
572
|
+
|
573
|
+
# Create a simple text view with minimal configuration
|
357
574
|
text_view = Gtk::TextView.new
|
358
575
|
text_view.editable = false
|
576
|
+
text_view.cursor_visible = false
|
359
577
|
text_view.wrap_mode = :word
|
360
|
-
|
361
|
-
#
|
578
|
+
|
579
|
+
# Use a fixed-width font
|
580
|
+
text_view.monospace = true
|
581
|
+
|
582
|
+
# Set colors - white text on black background
|
362
583
|
text_view.override_background_color(:normal, Gdk::RGBA.new(0, 0, 0, 1))
|
363
584
|
text_view.override_color(:normal, Gdk::RGBA.new(1, 1, 1, 1))
|
364
|
-
|
365
|
-
#
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
585
|
+
|
586
|
+
# Set font size explicitly
|
587
|
+
font_desc = Pango::FontDescription.new
|
588
|
+
font_desc.family = "Monospace"
|
589
|
+
font_desc.size = 12 * Pango::SCALE
|
590
|
+
text_view.override_font(font_desc)
|
591
|
+
|
592
|
+
# Set initial text
|
593
|
+
text_view.buffer.text = "Loading log data...\n"
|
594
|
+
|
595
|
+
# Add the text view to the scrolled window
|
596
|
+
scroll.add(text_view)
|
597
|
+
|
598
|
+
# Set up a timer to update the view more frequently
|
373
599
|
@view_timeouts ||= {}
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
update_log_view(text_view, buffer)
|
384
|
-
rescue StandardError => e
|
385
|
-
puts "Error updating log view: #{e}" if OPTIONS[:debug]
|
386
|
-
end
|
387
|
-
true # Keep the timeout active
|
600
|
+
timeout_id = GLib::Timeout.add(250) do
|
601
|
+
if text_view.destroyed? || scroll.destroyed?
|
602
|
+
@view_timeouts.delete(text_view.object_id)
|
603
|
+
false # Stop the timer
|
604
|
+
else
|
605
|
+
begin
|
606
|
+
update_log_view(text_view, buffer)
|
607
|
+
rescue => e
|
608
|
+
puts "DEBUG: Error updating log view: #{e.message}" if OPTIONS[:debug]
|
388
609
|
end
|
610
|
+
true # Continue the timer
|
389
611
|
end
|
390
|
-
|
612
|
+
end
|
613
|
+
|
614
|
+
# Store the timeout ID for cleanup
|
391
615
|
@view_timeouts[text_view.object_id] = timeout_id
|
392
|
-
|
393
|
-
# Clean up
|
616
|
+
|
617
|
+
# Clean up when the view is destroyed
|
394
618
|
text_view.signal_connect("destroy") do
|
395
|
-
if
|
396
|
-
|
397
|
-
GLib::Source.remove(timeout_id)
|
398
|
-
rescue StandardError
|
399
|
-
nil
|
400
|
-
end
|
619
|
+
if id = @view_timeouts.delete(text_view.object_id)
|
620
|
+
GLib::Source.remove(id) rescue nil
|
401
621
|
end
|
402
622
|
end
|
403
|
-
|
404
|
-
|
623
|
+
|
624
|
+
# Do an initial update
|
625
|
+
update_log_view(text_view, buffer)
|
626
|
+
|
627
|
+
# Return the scrolled window
|
405
628
|
scroll
|
406
629
|
end
|
407
630
|
|
408
|
-
|
409
|
-
# Basic ANSI colors
|
410
|
-
{
|
411
|
-
"31" => "#ff6b6b", # Brighter red
|
412
|
-
"32" => "#87ff87", # Brighter green
|
413
|
-
"33" => "#ffff87", # Brighter yellow
|
414
|
-
"34" => "#87d7ff", # Brighter blue
|
415
|
-
"35" => "#ff87ff", # Brighter magenta
|
416
|
-
"36" => "#87ffff", # Brighter cyan
|
417
|
-
"37" => "#ffffff" # White
|
418
|
-
}.each do |code, color|
|
419
|
-
buffer.create_tag("ansi_#{code}", foreground: color)
|
420
|
-
end
|
421
|
-
|
422
|
-
# Add more tags for bold, etc
|
423
|
-
buffer.create_tag("bold", weight: :bold)
|
424
|
-
end
|
631
|
+
# We're not using ANSI tags anymore since we're stripping ANSI codes
|
425
632
|
|
426
633
|
def update_log_view(text_view, buffer)
|
634
|
+
puts "DEBUG: update_log_view called" if OPTIONS[:debug]
|
635
|
+
|
636
|
+
# Basic validity checks
|
427
637
|
return if text_view.nil? || text_view.destroyed?
|
428
638
|
return if text_view.buffer.nil? || text_view.buffer.destroyed?
|
429
639
|
|
430
|
-
#
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
:@unicorn_line_count
|
437
|
-
end
|
438
|
-
)
|
439
|
-
current_offset = instance_variable_get(offset_var)
|
440
|
-
|
441
|
-
# Don't call if we've already processed all lines
|
442
|
-
return if buffer.size <= current_offset
|
443
|
-
|
444
|
-
adj = text_view&.parent&.vadjustment
|
445
|
-
was_at_bottom = (adj && adj.value >= adj.upper - adj.page_size - 50)
|
446
|
-
old_value = adj ? adj.value : 0
|
447
|
-
|
448
|
-
# Process only the new lines
|
449
|
-
new_lines = buffer[current_offset..-1]
|
450
|
-
new_lines.each do |line|
|
451
|
-
ansi_segments =
|
452
|
-
line.scan(/\e\[([0-9;]*)m([^\e]*)|\e\[K([^\e]*)|([^\e]+)/)
|
453
|
-
|
454
|
-
ansi_segments.each do |codes, text_part, clear_part, plain|
|
455
|
-
chunk = text_part || clear_part || plain.to_s
|
456
|
-
chunk_start_iter = text_view.buffer.end_iter
|
457
|
-
text_view.buffer.insert(chunk_start_iter, chunk)
|
458
|
-
|
459
|
-
# For each ANSI code, apply tags
|
460
|
-
if codes
|
461
|
-
codes
|
462
|
-
.split(";")
|
463
|
-
.each do |code|
|
464
|
-
case code
|
465
|
-
when "1"
|
466
|
-
text_view.buffer.apply_tag(
|
467
|
-
"bold",
|
468
|
-
chunk_start_iter,
|
469
|
-
text_view.buffer.end_iter
|
470
|
-
)
|
471
|
-
when "31".."37"
|
472
|
-
text_view.buffer.apply_tag(
|
473
|
-
"ansi_#{code}",
|
474
|
-
chunk_start_iter,
|
475
|
-
text_view.buffer.end_iter
|
476
|
-
)
|
477
|
-
end
|
478
|
-
end
|
479
|
-
end
|
640
|
+
# Debug buffer content
|
641
|
+
if OPTIONS[:debug]
|
642
|
+
puts "DEBUG: Buffer size: #{buffer.size}"
|
643
|
+
if buffer.size > 0
|
644
|
+
puts "DEBUG: First line: #{buffer.first.inspect}"
|
645
|
+
puts "DEBUG: Last line: #{buffer.last.inspect}"
|
480
646
|
end
|
481
647
|
end
|
482
648
|
|
483
|
-
#
|
484
|
-
|
649
|
+
# If buffer is empty, add a placeholder message
|
650
|
+
if buffer.empty?
|
651
|
+
buffer << "No log data available yet. Start Discourse to see logs.\n"
|
652
|
+
end
|
485
653
|
|
486
|
-
#
|
487
|
-
|
488
|
-
|
654
|
+
# Completely replace the buffer content with all lines
|
655
|
+
begin
|
656
|
+
# Make a local copy of the buffer to avoid race conditions
|
657
|
+
buffer_copy = buffer.dup
|
658
|
+
|
659
|
+
# Join all buffer lines into a single string
|
660
|
+
all_content = buffer_copy.join("")
|
661
|
+
|
662
|
+
# Strip ANSI codes
|
663
|
+
clean_content = all_content.gsub(/\e\[[0-9;]*[mK]/, '')
|
664
|
+
|
665
|
+
# Always update the content to ensure it's displayed
|
666
|
+
text_view.buffer.text = clean_content
|
667
|
+
|
668
|
+
puts "DEBUG: Updated buffer text (#{clean_content.length} chars)" if OPTIONS[:debug]
|
669
|
+
|
670
|
+
# Scroll to bottom
|
671
|
+
adj = text_view&.parent&.vadjustment
|
672
|
+
if adj
|
489
673
|
adj.value = adj.upper - adj.page_size
|
490
|
-
|
491
|
-
|
674
|
+
end
|
675
|
+
|
676
|
+
# Process any pending GTK events to ensure UI updates
|
677
|
+
while Gtk.events_pending?
|
678
|
+
Gtk.main_iteration_do(false)
|
679
|
+
end
|
680
|
+
rescue => e
|
681
|
+
puts "DEBUG: Error updating text view: #{e.message}" if OPTIONS[:debug]
|
682
|
+
puts e.backtrace.join("\n") if OPTIONS[:debug]
|
683
|
+
|
684
|
+
# Try a fallback approach
|
685
|
+
begin
|
686
|
+
text_view.buffer.text = "Error displaying log. See console for details.\n#{e.message}"
|
687
|
+
rescue => e2
|
688
|
+
puts "DEBUG: Even fallback approach failed: #{e2.message}" if OPTIONS[:debug]
|
492
689
|
end
|
493
690
|
end
|
494
691
|
end
|
@@ -624,6 +821,19 @@ module ::DiscourseSystray
|
|
624
821
|
if OPTIONS[:attach]
|
625
822
|
require "rb-inotify"
|
626
823
|
|
824
|
+
# Initialize GTK for attach mode too
|
825
|
+
Gtk.init
|
826
|
+
|
827
|
+
# Initialize empty buffers
|
828
|
+
@ember_output = []
|
829
|
+
@unicorn_output = []
|
830
|
+
|
831
|
+
# Show status window immediately in attach mode too
|
832
|
+
GLib::Idle.add do
|
833
|
+
show_status_window
|
834
|
+
false
|
835
|
+
end
|
836
|
+
|
627
837
|
notifier = INotify::Notifier.new
|
628
838
|
|
629
839
|
begin
|
@@ -642,18 +852,44 @@ module ::DiscourseSystray
|
|
642
852
|
Thread.new do
|
643
853
|
begin
|
644
854
|
while true
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
855
|
+
begin
|
856
|
+
# Use non-blocking read with timeout
|
857
|
+
ready = IO.select([pipe], nil, nil, 0.1)
|
858
|
+
if ready && ready[0].include?(pipe)
|
859
|
+
line = pipe.gets
|
860
|
+
if line
|
861
|
+
puts line
|
862
|
+
STDOUT.flush
|
863
|
+
|
864
|
+
# Process the line for our buffers
|
865
|
+
if line.include?("ember") || line.include?("Ember") || line.include?("ERROR: ...")
|
866
|
+
@ember_output << line
|
867
|
+
puts "DEBUG: Added to ember buffer: #{line}" if OPTIONS[:debug]
|
868
|
+
else
|
869
|
+
@unicorn_output << line
|
870
|
+
puts "DEBUG: Added to unicorn buffer: #{line}" if OPTIONS[:debug]
|
871
|
+
end
|
872
|
+
|
873
|
+
# Force GUI update immediately
|
874
|
+
GLib::Idle.add do
|
875
|
+
update_all_views
|
876
|
+
false
|
877
|
+
end
|
878
|
+
end
|
649
879
|
end
|
880
|
+
rescue IOError, Errno::EBADF => e
|
881
|
+
puts "DEBUG: Pipe read error: #{e.message}" if OPTIONS[:debug]
|
882
|
+
break
|
650
883
|
end
|
651
884
|
|
652
|
-
|
885
|
+
# Check if pipe still exists
|
653
886
|
unless File.exist?(PIPE_PATH)
|
654
887
|
puts "Pipe was deleted, exiting."
|
655
888
|
exit 0
|
656
889
|
end
|
890
|
+
|
891
|
+
# Small sleep to prevent CPU hogging
|
892
|
+
sleep 0.01
|
657
893
|
end
|
658
894
|
rescue EOFError, IOError
|
659
895
|
puts "Pipe closed, exiting."
|
@@ -661,6 +897,11 @@ module ::DiscourseSystray
|
|
661
897
|
end
|
662
898
|
end
|
663
899
|
|
900
|
+
# Start GTK main loop in a separate thread
|
901
|
+
gtk_thread = Thread.new do
|
902
|
+
Gtk.main
|
903
|
+
end
|
904
|
+
|
664
905
|
# Handle notifications in main thread
|
665
906
|
notifier.run
|
666
907
|
rescue Errno::ENOENT
|
@@ -695,20 +936,80 @@ module ::DiscourseSystray
|
|
695
936
|
|
696
937
|
# Setup systray icon and menu
|
697
938
|
init_systray
|
939
|
+
|
940
|
+
# Show status window immediately on startup
|
941
|
+
GLib::Idle.add do
|
942
|
+
show_status_window
|
943
|
+
false
|
944
|
+
end
|
698
945
|
|
699
946
|
# Start GTK main loop
|
700
947
|
Gtk.main
|
701
948
|
end
|
702
949
|
end
|
703
950
|
|
951
|
+
# Queue for pipe messages to avoid blocking
|
952
|
+
def initialize_pipe_queue
|
953
|
+
@pipe_queue = Queue.new
|
954
|
+
@pipe_thread = Thread.new do
|
955
|
+
loop do
|
956
|
+
begin
|
957
|
+
msg = @pipe_queue.pop
|
958
|
+
break if msg == :exit
|
959
|
+
|
960
|
+
if File.exist?(PIPE_PATH)
|
961
|
+
begin
|
962
|
+
# Use non-blocking write with timeout
|
963
|
+
Timeout.timeout(0.5) do
|
964
|
+
File.open(PIPE_PATH, "w") do |f|
|
965
|
+
f.puts(msg)
|
966
|
+
f.flush
|
967
|
+
end
|
968
|
+
end
|
969
|
+
rescue Timeout::Error
|
970
|
+
puts "Timeout writing to pipe" if OPTIONS[:debug]
|
971
|
+
rescue Errno::EPIPE, IOError => e
|
972
|
+
puts "Error writing to pipe: #{e}" if OPTIONS[:debug]
|
973
|
+
end
|
974
|
+
end
|
975
|
+
rescue => e
|
976
|
+
puts "Error in pipe thread: #{e}" if OPTIONS[:debug]
|
977
|
+
end
|
978
|
+
|
979
|
+
# Small sleep to prevent CPU hogging
|
980
|
+
sleep 0.01
|
981
|
+
end
|
982
|
+
end
|
983
|
+
end
|
984
|
+
|
704
985
|
def publish_to_pipe(msg)
|
705
|
-
return unless File.exist?(PIPE_PATH)
|
706
986
|
puts "Publish to pipe: #{msg}" if OPTIONS[:debug]
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
987
|
+
|
988
|
+
# Add to our buffers directly - do this immediately
|
989
|
+
if msg.include?("ember") || msg.include?("Ember") || msg.include?("ERROR: ...")
|
990
|
+
@ember_output ||= []
|
991
|
+
@ember_output << msg
|
992
|
+
# Trim if needed
|
993
|
+
if @ember_output.size > BUFFER_SIZE
|
994
|
+
@ember_output.shift(@ember_output.size - BUFFER_SIZE)
|
995
|
+
end
|
996
|
+
else
|
997
|
+
@unicorn_output ||= []
|
998
|
+
@unicorn_output << msg
|
999
|
+
# Trim if needed
|
1000
|
+
if @unicorn_output.size > BUFFER_SIZE
|
1001
|
+
@unicorn_output.shift(@unicorn_output.size - BUFFER_SIZE)
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
# Force GUI update immediately
|
1006
|
+
GLib::Idle.add do
|
1007
|
+
update_all_views if defined?(update_all_views)
|
1008
|
+
false
|
711
1009
|
end
|
1010
|
+
|
1011
|
+
# Queue the message for pipe writing in background
|
1012
|
+
@pipe_queue.push(msg) if @pipe_queue
|
712
1013
|
end
|
713
1014
|
|
714
1015
|
def handle_command(cmd)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: discourse-systray
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-03-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gtk3
|