Module: Lich::Common::Frontend
- Defined in:
- documented/common/front-end.rb
Constant Summary collapse
- CLIENT_STRING =
─── Client String ───────────────────────────────────────── Default client string (Wrayth identity) sent during handshake
"/FE:WRAYTH /VERSION:1.0.1.28 /P:WIN_UNKNOWN /XML"- XML_FRONTENDS =
─── Backward-Compatible Constants ───────────────────────── These arrays are derived from the registry for backward compatibility. External code may still reference these constants directly.
frontends_with_capability(:xml).freeze
- GSL_FRONTENDS =
frontends_with_capability(:gsl).freeze
- STREAM_FRONTENDS =
frontends_with_capability(:streams).freeze
- MONO_FRONTENDS =
frontends_with_capability(:mono).freeze
Class Method Summary collapse
-
.cleanup_session_file ⇒ void
Cleans up (deletes) the current session file if it exists.
- .client ⇒ Object
- .client=(value) ⇒ Object
-
.create_session_file(name, host, port, display_session: true) ⇒ void
Creates a session file for the specified frontend session.
-
.detect_pid ⇒ Integer?
Detects the frontend process ID (PID) based on the launch method.
-
.detect_platform ⇒ Symbol
Detects the current platform (Windows, macOS, Linux, or unsupported).
-
.ensure_windows_modules ⇒ Boolean
Ensures that the necessary Windows modules are loaded.
-
.frontends_with_capability(capability) ⇒ Array<String>
Returns a list of frontend names that support a specific capability.
-
.has_capability?(frontend_name, capability) ⇒ Boolean
Checks if a frontend has a specific capability.
-
.init_from_parent(parent_pid) ⇒ Integer
Initializes the frontend from a parent process ID.
-
.metadata_for(frontend_name, key) ⇒ String?
Retrieves metadata for a registered frontend by key.
-
.pid ⇒ Integer?
Retrieves the current frontend process ID (PID).
-
.pid=(value) ⇒ void
Sets the frontend process ID (PID).
-
.refocus ⇒ Boolean
Refocuses the frontend window based on the detected platform.
-
.refocus_callback ⇒ Proc
Returns a callback proc for refocusing the frontend window.
-
.refocus_linux(pid) ⇒ Boolean
Refocuses a Linux window based on the given process ID (PID).
-
.refocus_macos(pid) ⇒ Boolean
Refocuses a macOS window based on the given process ID (PID).
-
.refocus_windows(pid) ⇒ Boolean
Refocuses a Windows window based on the given process ID (PID).
-
.register(name, capabilities: [], metadata: {}) ⇒ void
Registers a new frontend with the specified capabilities and metadata.
-
.registered_frontends ⇒ Array<String>
Returns a list of all registered frontend names.
-
.resolve_linux_pid(pid) ⇒ Integer
Resolves a Linux process ID (PID) to find the correct one based on window visibility.
-
.resolve_pid(pid) ⇒ Integer
Resolves a process ID (PID) to find the correct one based on platform.
-
.resolve_windows_pid(pid) ⇒ Integer
Resolves a Windows process ID (PID) to find the correct one based on window visibility.
- .send_handshake(version_string) ⇒ Object
-
.session_file_location ⇒ String?
Returns the location of the current session file.
-
.set_from_client(pid) ⇒ Integer
Sets the frontend PID from the client.
-
.supports_gsl?(fe = $frontend) ⇒ Boolean
Checks if the specified frontend supports GSL capabilities.
-
.supports_mono?(fe = $frontend) ⇒ Boolean
Checks if the specified frontend supports mono capabilities.
-
.supports_room_window?(fe = $frontend) ⇒ Boolean
Checks if the specified frontend supports room window capabilities.
-
.supports_streams?(fe = $frontend) ⇒ Boolean
Checks if the specified frontend supports streams capabilities.
-
.supports_xml?(fe = $frontend) ⇒ Boolean
Checks if the specified frontend supports XML capabilities.
-
.windows_parent_pid(wmi, pid) ⇒ Integer
Retrieves the parent process ID for a given Windows process ID.
Class Method Details
.cleanup_session_file ⇒ void
This method returns an undefined value.
Cleans up (deletes) the current session file if it exists.
236 237 238 239 |
# File 'documented/common/front-end.rb', line 236 def self.cleanup_session_file return if @session_file.nil? File.delete(@session_file) if File.exist? @session_file end |
.client ⇒ Object
184 185 186 |
# File 'documented/common/front-end.rb', line 184 def self.client $frontend end |
.client=(value) ⇒ Object
188 189 190 |
# File 'documented/common/front-end.rb', line 188 def self.client=(value) $frontend = value end |
.create_session_file(name, host, port, display_session: true) ⇒ void
This method returns an undefined value.
Creates a session file for the specified frontend session.
215 216 217 218 219 220 221 222 223 224 |
# File 'documented/common/front-end.rb', line 215 def self.create_session_file(name, host, port, display_session: true) return if name.nil? FileUtils.mkdir_p @tmp_session_dir @session_file = File.join(@tmp_session_dir, "%s.session" % name.downcase.capitalize) session_descriptor = { name: name, host: host, port: port }.to_json puts "writing session descriptor to %s\n%s" % [@session_file, session_descriptor] if display_session File.open(@session_file, "w") do |fd| fd << session_descriptor end end |
.detect_pid ⇒ Integer?
Detects the frontend process ID (PID) based on the launch method.
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'documented/common/front-end.rb', line 303 def self.detect_pid # Return existing PID if already set current_pid = self.pid return current_pid if current_pid && current_pid > 0 # Try to detect based on launch method # This is a fallback for cases where init wasn't called parent_pid = Process.ppid resolved_pid = resolve_pid(parent_pid) if resolved_pid && resolved_pid > 0 self.pid = resolved_pid Lich.log "Frontend PID detected (fallback): #{resolved_pid}" if defined?(Lich.log) resolved_pid else Lich.log "Failed to detect frontend PID" if defined?(Lich.log) nil end end |
.detect_platform ⇒ Symbol
Detects the current platform (Windows, macOS, Linux, or unsupported).
358 359 360 361 362 363 364 365 |
# File 'documented/common/front-end.rb', line 358 def self.detect_platform case RUBY_PLATFORM when /mingw|mswin/ then :windows when /darwin/ then :macos when /linux/ then :linux else :unsupported end end |
.ensure_windows_modules ⇒ Boolean
Ensures that the necessary Windows modules are loaded.
572 573 574 575 576 577 578 |
# File 'documented/common/front-end.rb', line 572 def self.ensure_windows_modules # Check if modules exist - they should be defined at file load time if RUBY_PLATFORM =~ /mingw|mswin/ return defined?(::Win32Enum) && defined?(::WinAPI) end false end |
.frontends_with_capability(capability) ⇒ Array<String>
Returns a list of frontend names that support a specific capability.
103 104 105 |
# File 'documented/common/front-end.rb', line 103 def self.frontends_with_capability(capability) @registry.select { |_name, data| data[:capabilities].include?(capability.to_sym) }.keys end |
.has_capability?(frontend_name, capability) ⇒ Boolean
Checks if a frontend has a specific capability.
75 76 77 78 79 |
# File 'documented/common/front-end.rb', line 75 def self.has_capability?(frontend_name, capability) return false if frontend_name.nil? @registry[frontend_name.to_s.downcase][:capabilities].include?(capability.to_sym) end |
.init_from_parent(parent_pid) ⇒ Integer
Initializes the frontend from a parent process ID.
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'documented/common/front-end.rb', line 262 def self.init_from_parent(parent_pid) Lich.log "=== Frontend.init_from_parent called ===" Lich.log "Parent process PID: #{parent_pid}" # Let's see what process this actually is on Windows if RUBY_PLATFORM =~ /mingw|mswin/ begin require 'win32ole' wmi = WIN32OLE.connect('winmgmts://') rows = wmi.ExecQuery("SELECT Name, ProcessId FROM Win32_Process WHERE ProcessId=#{parent_pid}") row = rows.each.first rescue nil if row Lich.log "Parent process name: #{row.Name}" end rescue => e Lich.log "Could not get parent process name: #{e.}" end end resolved_pid = resolve_pid(parent_pid) Lich.log "resolve_pid(#{parent_pid}) returned: #{resolved_pid}" self.pid = resolved_pid Lich.log "Frontend PID set to: #{self.pid}" resolved_pid end |
.metadata_for(frontend_name, key) ⇒ String?
Retrieves metadata for a registered frontend by key.
86 87 88 89 90 |
# File 'documented/common/front-end.rb', line 86 def self.(frontend_name, key) return nil if frontend_name.nil? @registry[frontend_name.to_s.downcase][:metadata][key] end |
.pid ⇒ Integer?
Retrieves the current frontend process ID (PID).
245 246 247 |
# File 'documented/common/front-end.rb', line 245 def self.pid @pid_mutex.synchronize { @frontend_pid } end |
.pid=(value) ⇒ void
This method returns an undefined value.
Sets the frontend process ID (PID).
253 254 255 256 |
# File 'documented/common/front-end.rb', line 253 def self.pid=(value) value = value.to_i @pid_mutex.synchronize { @frontend_pid = value } end |
.refocus ⇒ Boolean
Refocuses the frontend window based on the detected platform.
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'documented/common/front-end.rb', line 326 def self.refocus pid = self.pid return false unless pid && pid > 0 case detect_platform when :windows refocus_windows(pid) when :macos refocus_macos(pid) when :linux refocus_linux(pid) else false end end |
.refocus_callback ⇒ Proc
Returns a callback proc for refocusing the frontend window.
345 346 347 348 349 350 351 352 353 |
# File 'documented/common/front-end.rb', line 345 def self.refocus_callback proc { if defined?(GLib) && GLib.respond_to?(:Idle) GLib::Idle.add(50) { self.refocus; false } else self.refocus end } end |
.refocus_linux(pid) ⇒ Boolean
Refocuses a Linux window based on the given process ID (PID).
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'documented/common/front-end.rb', line 553 def self.refocus_linux(pid) return false unless system('which xdotool > /dev/null 2>&1') _stdout, stderr, status = Open3.capture3('xdotool', 'search', '--pid', pid.to_s, 'windowactivate') if status.success? true else Lich.log "Error refocusing Linux: #{stderr}" if defined?(Lich.log) false end rescue => e Lich.log "Error refocusing Linux: #{e}" if defined?(Lich.log) false end |
.refocus_macos(pid) ⇒ Boolean
Refocuses a macOS window based on the given process ID (PID).
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 |
# File 'documented/common/front-end.rb', line 532 def self.refocus_macos(pid) return false unless system('which osascript > /dev/null 2>&1') script = %{tell application "System Events" to set frontmost of (first process whose unix id is #{pid}) to true} _stdout, stderr, status = Open3.capture3('osascript', '-e', script) if status.success? true else Lich.log "Error refocusing macOS: #{stderr}" if defined?(Lich.log) false end rescue => e Lich.log "Error refocusing macOS: #{e}" if defined?(Lich.log) false end |
.refocus_windows(pid) ⇒ Boolean
Refocuses a Windows window based on the given process ID (PID).
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
# File 'documented/common/front-end.rb', line 490 def self.refocus_windows(pid) ensure_windows_modules hwnd_buf = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP) enum_cb = Fiddle::Closure::BlockCaller.new( Fiddle::TYPE_INT, [Fiddle::TYPE_VOIDP, Fiddle::TYPE_LONG] ) do |hwnd, _| next 1 if ::WinAPI.IsWindowVisible(hwnd).zero? pid_tmp = [0].pack('L') ::WinAPI.GetWindowThreadProcessId(hwnd, pid_tmp) win_pid = pid_tmp.unpack1('L') if win_pid == pid hwnd_buf[0, Fiddle::SIZEOF_VOIDP] = [hwnd].pack('L!') 0 # stop enumeration else 1 # continue enumeration end end ::WinAPI.EnumWindows(enum_cb, 0) hwnd = hwnd_buf[0, Fiddle::SIZEOF_VOIDP].unpack1('L!') if hwnd != 0 ::WinAPI.SetForegroundWindow(hwnd) true else Lich.log "Frontend window for PID #{pid} not found" if defined?(Lich.log) false end rescue => e Lich.log "Error refocusing Windows: #{e}" if defined?(Lich.log) false end |
.register(name, capabilities: [], metadata: {}) ⇒ void
This method returns an undefined value.
Registers a new frontend with the specified capabilities and metadata.
64 65 66 67 68 |
# File 'documented/common/front-end.rb', line 64 def self.register(name, capabilities: [], metadata: {}) entry = @registry[name.to_s.downcase] entry[:capabilities].merge(capabilities.map(&:to_sym)) entry[:metadata].merge!() end |
.registered_frontends ⇒ Array<String>
Returns a list of all registered frontend names.
95 96 97 |
# File 'documented/common/front-end.rb', line 95 def self.registered_frontends @registry.keys end |
.resolve_linux_pid(pid) ⇒ Integer
Resolves a Linux process ID (PID) to find the correct one based on window visibility.
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
# File 'documented/common/front-end.rb', line 461 def self.resolve_linux_pid(pid) return pid unless system('which xdotool > /dev/null 2>&1') p = pid 16.times do # Check if this process has a window return p if system("xdotool search --pid #{p} >/dev/null 2>&1") # Walk up to parent process begin status = File.read("/proc/#{p}/status") parent = status[/PPid:\s+(\d+)/, 1].to_i rescue parent = 0 end return pid if parent.zero? || parent == p p = parent end pid # fallback rescue => e Lich.log "Error resolving Linux PID: #{e}" if defined?(Lich.log) pid end |
.resolve_pid(pid) ⇒ Integer
Resolves a process ID (PID) to find the correct one based on platform.
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'documented/common/front-end.rb', line 371 def self.resolve_pid(pid) pid = pid.to_i return pid if pid <= 0 # Return as-is if invalid # Use the FrontendPID resolver logic case detect_platform when :windows resolve_windows_pid(pid) when :linux resolve_linux_pid(pid) else # macOS/other: PID usually already owns the window pid end end |
.resolve_windows_pid(pid) ⇒ Integer
Resolves a Windows process ID (PID) to find the correct one based on window visibility.
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
# File 'documented/common/front-end.rb', line 391 def self.resolve_windows_pid(pid) Lich.log "=== resolve_windows_pid starting with PID: #{pid} ===" ensure_windows_modules require 'win32ole' rescue (return pid) begin wmi = WIN32OLE.connect('winmgmts://') p = pid 16.times do # Get process name for debugging rows = wmi.ExecQuery("SELECT Name FROM Win32_Process WHERE ProcessId=#{p}") row = rows.each.first rescue nil process_name = row ? row.Name : "unknown" Lich.log " Process name: #{process_name}" # Check if this process owns any visible window found = false cb = Fiddle::Closure::BlockCaller.new( Fiddle::TYPE_INT, [Fiddle::TYPE_VOIDP, Fiddle::TYPE_LONG] ) do |hwnd, _| next 1 if ::Win32Enum.IsWindowVisible(hwnd).zero? buf = [0].pack('L') ::Win32Enum.GetWindowThreadProcessId(hwnd, buf) if buf.unpack1('L') == p found = true Lich.log " Found visible window for PID #{p}" 0 # stop enumeration else 1 # continue enumeration end end ::Win32Enum.EnumWindows(cb, 0) if found Lich.log " Stopping at PID #{p} (#{process_name}) - has visible window" return p end # Walk up to parent process parent = windows_parent_pid(wmi, p) break if parent.nil? || parent.zero? || parent == p p = parent end rescue => e Lich.log "ERROR in resolve_windows_pid: #{e}" end Lich.log "Fallback: returning original PID #{pid}" pid end |
.send_handshake(version_string) ⇒ Object
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'documented/common/front-end.rb', line 192 def self.send_handshake(version_string) $_CLIENTBUFFER_.push(version_string.dup) Game._puts(version_string) 2.times do sleep 0.3 $_CLIENTBUFFER_.push("#{$cmd_prefix}\r\n") Game._puts($cmd_prefix) end ["#{$cmd_prefix}_injury 2", "#{$cmd_prefix}_flag Display Inventory Boxes 1", "#{$cmd_prefix}_flag Display Dialog Boxes 0"].each do |cmd| $_CLIENTBUFFER_.push(cmd) Game._puts(cmd) end end |
.session_file_location ⇒ String?
Returns the location of the current session file.
229 230 231 |
# File 'documented/common/front-end.rb', line 229 def self.session_file_location @session_file end |
.set_from_client(pid) ⇒ Integer
Sets the frontend PID from the client.
294 295 296 297 298 |
# File 'documented/common/front-end.rb', line 294 def self.set_from_client(pid) self.pid = pid Lich.log "Frontend PID set from client: #{pid}" if defined?(Lich.log) pid end |
.supports_gsl?(fe = $frontend) ⇒ Boolean
Checks if the specified frontend supports GSL capabilities.
156 157 158 |
# File 'documented/common/front-end.rb', line 156 def self.supports_gsl?(fe = $frontend) has_capability?(fe, :gsl) end |
.supports_mono?(fe = $frontend) ⇒ Boolean
Checks if the specified frontend supports mono capabilities.
172 173 174 |
# File 'documented/common/front-end.rb', line 172 def self.supports_mono?(fe = $frontend) has_capability?(fe, :mono) end |
.supports_room_window?(fe = $frontend) ⇒ Boolean
Checks if the specified frontend supports room window capabilities.
180 181 182 |
# File 'documented/common/front-end.rb', line 180 def self.supports_room_window?(fe = $frontend) has_capability?(fe, :room_window) end |
.supports_streams?(fe = $frontend) ⇒ Boolean
Checks if the specified frontend supports streams capabilities.
164 165 166 |
# File 'documented/common/front-end.rb', line 164 def self.supports_streams?(fe = $frontend) has_capability?(fe, :streams) end |
.supports_xml?(fe = $frontend) ⇒ Boolean
Checks if the specified frontend supports XML capabilities.
148 149 150 |
# File 'documented/common/front-end.rb', line 148 def self.supports_xml?(fe = $frontend) has_capability?(fe, :xml) end |
.windows_parent_pid(wmi, pid) ⇒ Integer
Retrieves the parent process ID for a given Windows process ID.
451 452 453 454 455 |
# File 'documented/common/front-end.rb', line 451 def self.windows_parent_pid(wmi, pid) rows = wmi.ExecQuery("SELECT ParentProcessId FROM Win32_Process WHERE ProcessId=#{pid}") row = rows.each.first rescue nil row ? row.ParentProcessId.to_i : 0 end |