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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/discourse_systray/systray.rb +456 -155
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5210b401c239734ab826f42710f3d925e65e52d62b9a5f8b3da18d56b874469b
4
- data.tar.gz: 838fac902e2cad6158ea5c5f52086047f4687b5871e1ce5c4b4989203d6e3eda
3
+ metadata.gz: f2a747e5c405b575a86d5ec8976fcd670f089b5af9bd5452b6077138c8a63830
4
+ data.tar.gz: 1a2354eece536b24f535b0bdd46612517685da8402703d07da40f136ad33d63c
5
5
  SHA512:
6
- metadata.gz: a4694037048949cbb48696ff280327b1d1f6f6349685de9e8a55fcfc81f27326e4812e526c0976c7615a0d0076543139c158744cd451841503b32f70d0963597
7
- data.tar.gz: 7193b7995de0c0ff81ca6224c12d478b916bb3972ec896a4266cfbf7a12dc1881091f9cc28c434d3fc65f190e0902e031dbcca6bb66ff705cd92f7891a53e766
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 = 2000
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
- stdin, stdout, stderr, wait_thr = Open3.popen3(command)
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
- wait_thr.value # Wait for process to finish
217
- is_ember = command.include?("ember-cli")
218
- @ember_running = false if is_ember
219
- @unicorn_running = false unless is_ember
220
- GLib::Idle.add do
221
- update_tab_labels if @notebook
222
- false
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
- # Monitor stdout - send to both console and UX buffer
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
- while line = stdout.gets
229
- buffer =
230
- command.include?("ember-cli") ? @ember_output : @unicorn_output
231
- publish_to_pipe(line)
232
- puts "[OUT] #{line}" if OPTIONS[:debug]
233
- buffer << line
234
- buffer.shift if buffer.size > BUFFER_SIZE
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 - send to both console and UX buffer
378
+ # Monitor stderr
244
379
  Thread.new do
245
- while line = stderr.gets
246
- buffer =
247
- command.include?("ember-cli") ? @ember_output : @unicorn_output
248
- publish_to_pipe("ERROR: #{line}")
249
- puts "[ERR] #{line}" if OPTIONS[:debug]
250
- buffer << line
251
- buffer.shift if buffer.size > BUFFER_SIZE
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
- return unless @ember_view.child && @unicorn_view.child
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
- if @ember_view.visible? && @ember_view.child.visible?
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
- if @unicorn_view.visible? && @unicorn_view.child.visible?
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
- rescue StandardError => e
351
- puts "Error updating views: #{e}" if OPTIONS[:debug]
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
- # Set white text on black background
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
- # Create text tags for colors
366
- _tag_table = text_view.buffer.tag_table
367
- create_ansi_tags(text_view.buffer)
368
-
369
- # Initial text
370
- update_log_view(text_view, buffer)
371
-
372
- # Store timeouts in instance variable for proper cleanup
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
- # Set up periodic refresh with validity check
376
- timeout_id =
377
- GLib::Timeout.add(500) do
378
- if text_view&.parent.nil? || text_view.destroyed?
379
- @view_timeouts.delete(text_view.object_id)
380
- false # Stop the timeout if view is destroyed
381
- else
382
- begin
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 timeout when view is destroyed
616
+
617
+ # Clean up when the view is destroyed
394
618
  text_view.signal_connect("destroy") do
395
- if timeout_id = @view_timeouts.delete(text_view.object_id)
396
- begin
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
- scroll.add(text_view)
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
- def create_ansi_tags(buffer)
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
- # Determine which offset counter to use
431
- offset_var =
432
- (
433
- if buffer.equal?(@ember_output)
434
- :@ember_line_count
435
- else
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
- # Update our offset counter
484
- instance_variable_set(offset_var, buffer.size)
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
- # Restore scroll position
487
- if adj
488
- if was_at_bottom
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
- else
491
- adj.value = old_value
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
- if IO.select([pipe], nil, nil, 0.5)
646
- while line = pipe.gets
647
- puts line
648
- STDOUT.flush
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
- sleep 0.1
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
- begin
708
- File.open(PIPE_PATH, "w") { |f| f.puts(msg) }
709
- rescue Errno::EPIPE, IOError => e
710
- puts "Error writing to pipe: #{e}" if OPTIONS[:debug]
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.2
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-01-26 00:00:00.000000000 Z
11
+ date: 2025-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gtk3