Module: Lich::Common::Frontend Private
- Defined in:
- documented/common/frontend.rb,
documented/common/frontend.rb
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
The main Frontend module (continued from earlier namespace definition).
This reopening contains the runtime registry implementation, user configuration management, capability queries, and platform-specific window focus logic.
Constant Summary collapse
- PLATFORM_KEYS =
Canonical platform keys used by frontend discovery and launch-plan metadata.
%i[darwin linux windows unsupported].freeze
- CAPABILITY_VOCABULARY =
Valid capability symbols that frontends may declare.
:xml– frontend supports the XML stream protocol:gsl– frontend supports GSL (Gemstone Scripting Language):streams– frontend supports streaming data (Avalon, Stormfront):mono– frontend supports monospace text rendering:room_window– frontend can display the room window independently:sentinel– frontend is the Sentinel client
%i[xml gsl streams mono room_window sentinel].freeze
- BUILT_IN_DEFINITION_FILES =
Manifest of built-in frontend definitions.
Each tuple is [frontend_id, filename] where frontend_id is the stable catalog identifier and filename is the relative path (without .rb) in the frontend/ directory. Order is preserved for the GTK launcher UI and legacy capability constants.
[ %w[stormfront wrayth], %w[profanity profanity], %w[genie genie], %w[frostbite frostbite], %w[suks suks], %w[wizard wizard], %w[avalon avalon], %w[saga saga] ].freeze
- BUILT_IN_FRONTEND_IDS =
Canonical identifiers for Lich's built-in frontend catalog.
These identifiers are reserved and cannot be overridden by user-defined frontends. User configuration may only supply launch-field overrides for built-ins, not replace identity or protocol capabilities.
BUILT_IN_DEFINITION_FILES.map(&:first).freeze
- ORIGIN_SENTINEL =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
"\x1f"- SAGA_LICH_LAUNCH_ENVIRONMENT =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Environment variables for the Saga launcher on Linux.
Extracted from the built-in Saga definition at module load time. Set when launching Saga to ensure proper library paths and runtime configuration.
definition_for(:saga) .dig(:metadata, :launch_plans, :linux, :environment)
- CLIENT_STRING =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
--- Client String ----------------------------------------- Default client string (Wrayth identity) sent during handshake
"/FE:WRAYTH /VERSION:1.0.1.28 /P:WIN_UNKNOWN /XML"- XML_FRONTENDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
--- Backward-Compatible Constants ------------------------- These frozen arrays preserve the built-in capability membership exposed by older Lich versions. They intentionally remain stable when user-defined frontends are loaded. Runtime callers that need current custom frontend membership must use frontends_with_capability or supports_*? instead.
frontends_with_capability(:xml).freeze
- GSL_FRONTENDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Cached list of frontends that support GSL (Gemstone Scripting Language).
This list is frozen at module load and does not update when user-defined frontends are loaded. Scripts requiring current custom frontend membership must use .frontends_with_capability(:gsl) instead.
frontends_with_capability(:gsl).freeze
- STREAM_FRONTENDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Cached list of frontends that support streaming data.
This list is frozen at module load and does not update when user-defined frontends are loaded. Scripts requiring current custom frontend membership must use .frontends_with_capability(:streams) instead.
frontends_with_capability(:streams).freeze
- MONO_FRONTENDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Cached list of frontends that support monospace text rendering.
This list is frozen at module load and does not update when user-defined frontends are loaded. Scripts requiring current custom frontend membership must use .frontends_with_capability(:mono) instead.
frontends_with_capability(:mono).freeze
- SENTINEL_FRONTENDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Cached list of frontends that are the Sentinel client.
This list is frozen at module load and does not update when user-defined frontends are loaded. Scripts requiring current custom frontend membership must use .frontends_with_capability(:sentinel) instead.
frontends_with_capability(:sentinel).freeze
Class Method Summary collapse
-
.built_in_frontends ⇒ Array<String>
private
Returns the stable identifiers owned by Lich's built-in catalog.
-
.canonical_name(frontend_name) ⇒ String
private
Returns the stable catalog identifier for a frontend or alias.
-
.capability_vocabulary ⇒ Array<Symbol>
private
Returns the capabilities users may declare for custom frontends.
-
.cleanup_session_file ⇒ void
private
Deletes the session descriptor file created by .create_session_file.
-
.client ⇒ Object
private
Accessor for the current frontend identity ($frontend global).
-
.client=(value) ⇒ Object
private
Setter for the current frontend identity.
-
.create_session_file(name, host, port, display_session: true) ⇒ void
private
Writes a session descriptor file for detachable clients (e.g., Saga).
-
.definition_for(frontend_name) ⇒ Hash
private
Returns an immutable catalog definition for a registered frontend.
-
.definitions(gui_selectable: nil) ⇒ Array<Hash>
private
Returns immutable catalog definitions, optionally restricted to those intended for the graphical launcher.
-
.detect_pid ⇒ Integer?
private
Detect and store the frontend process ID Uses various methods depending on how Lich was launched.
-
.detect_platform ⇒ Symbol
private
Detect the current platform.
-
.display_name(frontend_name) ⇒ String
private
Returns the catalog display name, with a stable fallback for legacy saved entries that predate the catalog.
-
.ensure_windows_modules ⇒ Object
private
Ensure Windows modules are loaded (they're defined at top level).
-
.frontends_with_capability(capability) ⇒ Array<String>
private
Returns all frontends that have a specific capability.
-
.has_capability?(frontend_name, capability) ⇒ Boolean
private
Checks if a frontend has a specific capability.
-
.init_from_parent(parent_pid) ⇒ Integer?
private
Initialize PID from parent process (for Warlock).
-
.metadata_for(frontend_name, key) ⇒ Object?
private
Retrieves a metadata value for a given frontend.
-
.native_windows_runtime? ⇒ Boolean
Native user32 bindings require a Windows MRI ABI, not merely a Windows host.
-
.pid ⇒ Integer?
private
Get the current frontend PID.
-
.pid=(value) ⇒ Integer
private
Set the frontend PID.
-
.platform_key ⇒ Symbol
private
Returns the canonical platform key used by frontend discovery and launch-plan metadata.
-
.player_id_tag(player_id) ⇒ Object
private
Build the
re-emit tag for a detachable client (e.g. Saga). -
.refocus ⇒ Boolean
private
Refocus the frontend window.
-
.refocus_callback ⇒ Proc
private
Create a callback for GTK windows to refocus on click.
-
.refocus_linux(pid) ⇒ Object
private
Linux refocus implementation.
-
.refocus_macos(pid) ⇒ Object
private
macOS refocus implementation.
-
.refocus_windows(pid) ⇒ Object
private
Windows refocus implementation.
-
.register(name, capabilities: [], metadata: {}) ⇒ void
private
Registers a frontend with its capabilities and metadata.
-
.registered_frontends ⇒ Array<String>
private
Returns every recognized frontend name: canonical catalog identifiers followed by their accepted aliases.
-
.replace_user_configuration!(built_in_overrides:, custom_definitions:) ⇒ void
private
Replaces the complete process-local user configuration in one operation.
-
.resolve_linux_pid(pid) ⇒ Object
private
Linux-specific PID resolution.
-
.resolve_pid(pid) ⇒ Integer
private
Resolve PID by walking up process tree to find window owner.
-
.resolve_windows_pid(pid) ⇒ Object
private
Windows-specific PID resolution.
-
.seal_built_in_catalog! ⇒ void
private
Captures immutable built-in defaults after all built-ins are registered.
-
.send_handshake(version_string) ⇒ Object
private
Send version string, ready signals, and setup commands to the game server.
-
.session_file_location ⇒ String?
private
Returns the path to the most recently created session descriptor file.
-
.set_from_client(pid) ⇒ Integer
private
Set PID from a detachable frontend such as Profanity or Saga.
-
.supports_gsl?(fe = $frontend) ⇒ Boolean
private
Checks whether a frontend supports GSL (Gemstone Scripting Language).
-
.supports_mono?(fe = $frontend) ⇒ Boolean
private
Checks whether a frontend supports monospace text rendering.
-
.supports_room_window?(fe = $frontend) ⇒ Boolean
private
Checks whether a frontend can display the room window independently.
-
.supports_sentinel?(fe = $frontend) ⇒ Boolean
private
Checks whether a frontend is the Sentinel client.
-
.supports_streams?(fe = $frontend) ⇒ Boolean
private
Checks whether a frontend supports streaming data.
-
.supports_xml?(fe = $frontend) ⇒ Boolean
private
Checks whether a frontend supports the XML stream protocol.
-
.user_definition_id_available?(frontend_name) ⇒ Boolean
private
Returns whether an identifier is available for a settings-owned custom frontend.
-
.validate_platform_key!(key) ⇒ Symbol
private
Validates a canonical platform key used by discovery and launch plans.
-
.windows_parent_pid(wmi, pid) ⇒ Object
private
Get parent process ID on Windows.
-
.windows_platform? ⇒ Boolean
private
Returns whether the current host is classified as Windows.
Class Method Details
.built_in_frontends ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the stable identifiers owned by Lich's built-in catalog. User configuration may override only explicitly supported launch fields; it cannot replace built-in identity, protocol capabilities, or defaults.
193 194 195 |
# File 'documented/common/frontend.rb', line 193 def self.built_in_frontends BUILT_IN_FRONTEND_IDS end |
.canonical_name(frontend_name) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the stable catalog identifier for a frontend or alias. Unknown values are normalized but are not registered.
329 330 331 332 333 334 |
# File 'documented/common/frontend.rb', line 329 def self.canonical_name(frontend_name) registry_synchronize do key = frontend_name.to_s.downcase @aliases.fetch(key, key) end end |
.capability_vocabulary ⇒ Array<Symbol>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the capabilities users may declare for custom frontends. Runtime-only implementation details are deliberately excluded.
185 186 187 |
# File 'documented/common/frontend.rb', line 185 def self.capability_vocabulary CAPABILITY_VOCABULARY end |
.cleanup_session_file ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Deletes the session descriptor file created by .create_session_file.
Safe to call multiple times or when no session file exists.
741 742 743 744 |
# File 'documented/common/frontend.rb', line 741 def self.cleanup_session_file return if @session_file.nil? File.delete(@session_file) if File.exist? @session_file end |
.client ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Accessor for the current frontend identity ($frontend global)
678 679 680 |
# File 'documented/common/frontend.rb', line 678 def self.client $frontend end |
.client=(value) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Setter for the current frontend identity
683 684 685 |
# File 'documented/common/frontend.rb', line 683 def self.client=(value) $frontend = value end |
.create_session_file(name, host, port, display_session: true) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Writes a session descriptor file for detachable clients (e.g., Saga).
Creates the file at %TEMP%/simutronics/sessions/Name.session on Windows or
/tmp/simutronics/sessions/Name.session on Unix, containing a JSON object with
name, host, and port. Stores the path internally for later cleanup.
718 719 720 721 722 723 724 725 726 727 |
# File 'documented/common/frontend.rb', line 718 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 |
.definition_for(frontend_name) ⇒ Hash
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns an immutable catalog definition for a registered frontend.
Accepted inputs are a non-empty String or Symbol naming an existing registry entry. Invalid or unknown identifiers raise ArgumentError. This method performs no discovery and persists nothing.
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
# File 'documented/common/frontend.rb', line 370 def self.definition_for(frontend_name) registry_synchronize do key = canonical_name(frontend_name) raise ArgumentError, 'frontend name must not be empty' if key.empty? raise ArgumentError, "unknown frontend: #{frontend_name}" unless @registry.key?(key) @definitions[key] ||= Lich::Util.deep_freeze( { id: key, capabilities: @registry.fetch(key)[:capabilities].to_a, metadata: deep_copy(@registry.fetch(key)[:metadata]) } ) end end |
.definitions(gui_selectable: nil) ⇒ Array<Hash>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns immutable catalog definitions, optionally restricted to those intended for the graphical launcher.
391 392 393 394 395 396 397 398 399 400 |
# File 'documented/common/frontend.rb', line 391 def self.definitions(gui_selectable: nil) registry_synchronize do definitions = @registry.keys.map { |name| definition_for(name) } next definitions if gui_selectable.nil? definitions.select do |definition| definition.dig(:metadata, :gui_selectable) == gui_selectable end end end |
.detect_pid ⇒ Integer?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Detect and store the frontend process ID Uses various methods depending on how Lich was launched
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 |
# File 'documented/common/frontend.rb', line 804 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
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Detect the current platform
856 857 858 859 |
# File 'documented/common/frontend.rb', line 856 def self.detect_platform key = platform_key key == :darwin ? :macos : key end |
.display_name(frontend_name) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the catalog display name, with a stable fallback for legacy saved entries that predate the catalog.
437 438 439 440 441 |
# File 'documented/common/frontend.rb', line 437 def self.display_name(frontend_name) definition_for(frontend_name).dig(:metadata, :display_name) || frontend_name.to_s.capitalize rescue ArgumentError frontend_name.to_s.capitalize end |
.ensure_windows_modules ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Ensure Windows modules are loaded (they're defined at top level)
1044 1045 1046 1047 1048 |
# File 'documented/common/frontend.rb', line 1044 def self.ensure_windows_modules return false unless native_windows_runtime? defined?(::Win32Enum) && defined?(::WinAPI) end |
.frontends_with_capability(capability) ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns all frontends that have a specific capability.
453 454 455 456 457 458 459 460 461 |
# File 'documented/common/frontend.rb', line 453 def self.frontends_with_capability(capability) registry_synchronize do canonical = @registry.select { |_name, data| data[:capabilities].include?(capability.to_sym) }.keys aliases = @aliases.filter_map do |alias_name, name| alias_name if canonical.include?(name) end canonical + aliases end end |
.has_capability?(frontend_name, capability) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Checks if a frontend has a specific capability.
340 341 342 343 344 345 346 347 |
# File 'documented/common/frontend.rb', line 340 def self.has_capability?(frontend_name, capability) return false if frontend_name.nil? registry_synchronize do entry = @registry[canonical_name(frontend_name)] entry ? entry[:capabilities].include?(capability.to_sym) : false end end |
.init_from_parent(parent_pid) ⇒ Integer?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Initialize PID from parent process (for Warlock)
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 |
# File 'documented/common/frontend.rb', line 764 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 windows_platform? 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 StandardError, LoadError => 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) ⇒ Object?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Retrieves a metadata value for a given frontend.
353 354 355 356 357 358 359 |
# File 'documented/common/frontend.rb', line 353 def self.(frontend_name, key) return nil if frontend_name.nil? definition_for(frontend_name).dig(:metadata, key) rescue ArgumentError nil end |
.native_windows_runtime? ⇒ Boolean
Native user32 bindings require a Windows MRI ABI, not merely a Windows host.
62 63 64 |
# File 'documented/common/frontend.rb', line 62 def self.native_windows_runtime? OS.host_os.to_s.match?(/mingw|mswin/i) end |
.pid ⇒ Integer?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Get the current frontend PID
750 751 752 |
# File 'documented/common/frontend.rb', line 750 def self.pid @pid_mutex.synchronize { @frontend_pid } end |
.pid=(value) ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Set the frontend PID
757 758 759 760 |
# File 'documented/common/frontend.rb', line 757 def self.pid=(value) value = value.to_i @pid_mutex.synchronize { @frontend_pid = value } end |
.platform_key ⇒ Symbol
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the canonical platform key used by frontend discovery and launch-plan metadata.
406 407 408 409 410 411 412 |
# File 'documented/common/frontend.rb', line 406 def self.platform_key return :darwin if OS.mac? return :linux if OS.linux? return :windows if OS.windows? :unsupported end |
.player_id_tag(player_id) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Build the
Lich consumes the game's one-time
Returns the tag string only when player_id is a bare numeric id (the form the game sends). Returns nil otherwise, so callers skip emitting an empty or malformed tag before login has populated the id.
670 671 672 673 674 675 |
# File 'documented/common/frontend.rb', line 670 def self.player_id_tag(player_id) id = player_id.to_s return nil unless id =~ /\A\d+\z/ "<playerID id='#{id}'/>" end |
.refocus ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Refocus the frontend window
826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 |
# File 'documented/common/frontend.rb', line 826 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
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Create a callback for GTK windows to refocus on click
844 845 846 847 848 849 850 851 852 |
# File 'documented/common/frontend.rb', line 844 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) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Linux refocus implementation
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 |
# File 'documented/common/frontend.rb', line 1027 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) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
macOS refocus implementation
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 |
# File 'documented/common/frontend.rb', line 1009 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) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Windows refocus implementation
970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 |
# File 'documented/common/frontend.rb', line 970 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 is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Registers a frontend with its capabilities and metadata.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'documented/common/frontend.rb', line 162 def self.register(name, capabilities: [], metadata: {}) registry_synchronize do key = name.to_s.downcase raise ArgumentError, 'frontend name must not be empty' if key.empty? aliases = Array([:aliases]).map { |alias_name| alias_name.to_s.downcase } settings_owned = Array(@user_frontend_ids) if settings_owned.include?(key) || aliases.any? { |alias_name| settings_owned.include?(alias_name) } raise ArgumentError, "frontend identifier is managed by user settings: #{key}" end entry = (@registry[key] ||= { capabilities: Set.new, metadata: {} }) entry[:capabilities].merge(capabilities.map(&:to_sym)) entry[:metadata].merge!(deep_copy()) aliases.each { |alias_name| @aliases[alias_name] = key } @definitions.delete(key) nil end end |
.registered_frontends ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns every recognized frontend name: canonical catalog identifiers followed by their accepted aliases.
446 447 448 |
# File 'documented/common/frontend.rb', line 446 def self.registered_frontends registry_synchronize { @registry.keys + @aliases.keys } end |
.replace_user_configuration!(built_in_overrides:, custom_definitions:) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Replaces the complete process-local user configuration in one operation. Built-ins are restored from their sealed defaults before launch-only overrides are applied, and custom definitions from an earlier load are removed before the new set is registered.
FrontendSettings owns validation and persistence. This method is the narrow application seam that keeps catalog reloads deterministic.
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'documented/common/frontend.rb', line 225 def self.replace_user_configuration!(built_in_overrides:, custom_definitions:) registry_synchronize do raise 'built-in frontend catalog has not been sealed' unless @built_in_registry snapshot = { registry: deep_copy(@registry), aliases: deep_copy(@aliases), definitions: @definitions.dup, user_frontend_ids: Array(@user_frontend_ids).dup } begin replace_user_configuration_unlocked!( built_in_overrides: built_in_overrides, custom_definitions: custom_definitions ) rescue StandardError @registry = snapshot[:registry] @aliases = snapshot[:aliases] @definitions = snapshot[:definitions] @user_frontend_ids = snapshot[:user_frontend_ids] raise end end end |
.resolve_linux_pid(pid) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Linux-specific PID resolution
944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 |
# File 'documented/common/frontend.rb', line 944 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
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Resolve PID by walking up process tree to find window owner
864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 |
# File 'documented/common/frontend.rb', line 864 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) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Windows-specific PID resolution
881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 |
# File 'documented/common/frontend.rb', line 881 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 |
.seal_built_in_catalog! ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Captures immutable built-in defaults after all built-ins are registered.
309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'documented/common/frontend.rb', line 309 def self.seal_built_in_catalog! registry_synchronize do @built_in_registry = BUILT_IN_FRONTEND_IDS.each_with_object({}) do |frontend_id, result| result[frontend_id] = deep_copy(@registry.fetch(frontend_id)) end @built_in_aliases = @aliases.select do |_alias_name, frontend_id| BUILT_IN_FRONTEND_IDS.include?(frontend_id) end Lich::Util.deep_freeze(@built_in_registry) Lich::Util.deep_freeze(@built_in_aliases) @user_frontend_ids = [] nil end end |
.send_handshake(version_string) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Send version string, ready signals, and setup commands to the game server. Used during login handshake for wizard/avalon/frostbite frontends.
689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 |
# File 'documented/common/frontend.rb', line 689 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?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the path to the most recently created session descriptor file.
732 733 734 |
# File 'documented/common/frontend.rb', line 732 def self.session_file_location @session_file end |
.set_from_client(pid) ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Set PID from a detachable frontend such as Profanity or Saga.
795 796 797 798 799 |
# File 'documented/common/frontend.rb', line 795 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
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Checks whether a frontend supports GSL (Gemstone Scripting Language).
624 625 626 |
# File 'documented/common/frontend.rb', line 624 def self.supports_gsl?(fe = $frontend) has_capability?(fe, :gsl) end |
.supports_mono?(fe = $frontend) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Checks whether a frontend supports monospace text rendering.
640 641 642 |
# File 'documented/common/frontend.rb', line 640 def self.supports_mono?(fe = $frontend) has_capability?(fe, :mono) end |
.supports_room_window?(fe = $frontend) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Checks whether a frontend can display the room window independently.
648 649 650 |
# File 'documented/common/frontend.rb', line 648 def self.supports_room_window?(fe = $frontend) has_capability?(fe, :room_window) end |
.supports_sentinel?(fe = $frontend) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Checks whether a frontend is the Sentinel client.
656 657 658 |
# File 'documented/common/frontend.rb', line 656 def self.supports_sentinel?(fe = $frontend) has_capability?(fe, :sentinel) end |
.supports_streams?(fe = $frontend) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Checks whether a frontend supports streaming data.
632 633 634 |
# File 'documented/common/frontend.rb', line 632 def self.supports_streams?(fe = $frontend) has_capability?(fe, :streams) end |
.supports_xml?(fe = $frontend) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Checks whether a frontend supports the XML stream protocol.
616 617 618 |
# File 'documented/common/frontend.rb', line 616 def self.supports_xml?(fe = $frontend) has_capability?(fe, :xml) end |
.user_definition_id_available?(frontend_name) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns whether an identifier is available for a settings-owned custom frontend. An identifier already owned by the current settings document remains available so reloads can replace it without colliding with themselves. Registry entries and aliases owned elsewhere are reserved.
204 205 206 207 208 209 210 211 212 |
# File 'documented/common/frontend.rb', line 204 def self.user_definition_id_available?(frontend_name) registry_synchronize do key = frontend_name.to_s.downcase next false if key.empty? next true if Array(@user_frontend_ids).include?(key) !@registry.key?(key) && !@aliases.key?(key) end end |
.validate_platform_key!(key) ⇒ Symbol
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Validates a canonical platform key used by discovery and launch plans.
419 420 421 422 423 |
# File 'documented/common/frontend.rb', line 419 def self.validate_platform_key!(key) return key if PLATFORM_KEYS.include?(key) raise ArgumentError, "invalid platform key: #{key.inspect}" end |
.windows_parent_pid(wmi, pid) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Get parent process ID on Windows
937 938 939 940 941 |
# File 'documented/common/frontend.rb', line 937 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 |
.windows_platform? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns whether the current host is classified as Windows.
428 429 430 |
# File 'documented/common/frontend.rb', line 428 def self.windows_platform? platform_key == :windows end |