Class: RosettAi::Desktop::DbusClient

Inherits:
Object
  • Object
show all
Defined in:
lib/rosett_ai/desktop/dbus_client.rb

Overview

D-Bus client for communicating with be.neatnerds.rosettai service.

SAFETY: This client is designed to NEVER raise exceptions to callers. All methods return safe default values on failure. This is critical for GUI stability - a D-Bus failure should never crash the app.

All GTK4/Qt6 desktop apps use this client instead of calling Ruby Rosett-AI modules directly. This ensures the D-Bus service is the single source of truth.

Constant Summary collapse

BUS_NAME =

Returns D-Bus well-known bus name.

Returns:

  • (String)

    D-Bus well-known bus name.

'be.neatnerds.rosettai'
OBJECT_PATH =

Returns D-Bus object path.

Returns:

  • (String)

    D-Bus object path.

'/be/neatnerds/rosett-ai'
MANAGER_IFACE =

Returns Manager iface constant.

Returns:

  • (String)

    Manager iface constant.

'be.neatnerds.rosettai.Manager'
FOCUS_IFACE =

Returns Focus iface constant.

Returns:

  • (String)

    Focus iface constant.

'be.neatnerds.rosettai.FocusMonitor'
SAFE_STATUS =

Default safe return values

{ 'error' => 'Service not available', 'version' => 'unknown' }.freeze
SAFE_FOCUS =

Returns Safe focus constant.

Returns:

  • (Array)

    Safe focus constant.

['', ''].freeze
SAFE_COMPILE_RESULT =

Returns Safe compile result constant.

Returns:

  • (String)

    Safe compile result constant.

'Error: D-Bus service not available'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeDbusClient

Returns a new instance of DbusClient.



39
40
41
42
43
44
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 39

def initialize
  @bus = nil
  @object = nil
  @connected = false
  @last_error = nil
end

Instance Attribute Details

#busObject (readonly)

Returns the value of attribute bus.



37
38
39
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 37

def bus
  @bus
end

#last_errorObject (readonly)

Returns the value of attribute last_error.



37
38
39
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 37

def last_error
  @last_error
end

Instance Method Details

#compileString

Run compilation via D-Bus. SAFETY: Never raises, returns error string on failure.

Returns:

  • (String)

    compilation result or error message



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 112

def compile
  GuiLogger.dbus_call(:compile, interface: MANAGER_IFACE)
  return SAFE_COMPILE_RESULT unless safe_connect

  @object.default_iface = MANAGER_IFACE
  result = @object.Compile
  output = result.is_a?(Array) ? result.first : result.to_s
  GuiLogger.dbus_call(:compile, status: :success, result_length: output.length)
  output
rescue StandardError => e
  log_error('Compile failed', e)
  "Error: #{e.message}"
end

#connectBoolean

Connect to the D-Bus session bus and get service proxy.

Returns:

  • (Boolean)

    true if connected successfully



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 49

def connect
  return true if @connected

  GuiLogger.dbus_call(:connect, bus_name: BUS_NAME)
  @last_error = nil
  @bus = ::DBus::SessionBus.instance
  service = @bus.service(BUS_NAME)
  @object = service.object(OBJECT_PATH)
  @object.introspect
  @connected = true
  GuiLogger.dbus_call(:connect, status: :success)
  true
rescue ::DBus::Error => e
  @last_error = e.message
  log_error('D-Bus connection failed', e)
  @connected = false
  false
rescue StandardError => e
  @last_error = e.message
  log_error('Unexpected error during D-Bus connection', e)
  @connected = false
  false
end

#connected?Boolean

Check if connected to D-Bus service.

Returns:

  • (Boolean)


76
77
78
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 76

def connected?
  @connected
end

#current_focusArray<String>

Get current focus from FocusMonitor interface. SAFETY: Never raises, returns empty array on failure.

Returns:

  • (Array<String>)

    [app_id, window_title]



146
147
148
149
150
151
152
153
154
155
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 146

def current_focus
  return SAFE_FOCUS.dup unless safe_connect

  @object.default_iface = FOCUS_IFACE
  result = @object.GetCurrentFocus
  result.is_a?(Array) && result.size >= 2 ? result : SAFE_FOCUS.dup
rescue StandardError => e
  log_error('GetCurrentFocus failed', e)
  SAFE_FOCUS.dup
end

#disconnectObject

Disconnect from D-Bus. SAFETY: Never raises.



227
228
229
230
231
232
233
234
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 227

def disconnect
  @connected = false
  @object = nil
  @bus = nil
  @last_error = nil
rescue StandardError => e
  log_error('Disconnect failed', e)
end

#list_enginesArray<Hash>

List available engines from D-Bus service. SAFETY: Never raises, returns empty array on failure.

Returns:

  • (Array<Hash>)

    engine hashes with :name and :display_name keys



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 195

def list_engines
  return [] unless safe_connect

  @object.default_iface = MANAGER_IFACE
  result = @object.ListEngines
  tuples = result.is_a?(Array) ? result.first : []
  (tuples || []).map { |name, display| { name: name, display_name: display } }
rescue StandardError => e
  log_error('ListEngines failed', e)
  []
end

#on_context_changed {|context_name| ... } ⇒ Boolean

Subscribe to ContextChanged signal. SAFETY: Never raises, silently fails if service unavailable.

Yields:

  • (context_name)

    called when context changes

Returns:

  • (Boolean)

    true if subscription succeeded



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 179

def on_context_changed(&block)
  return false unless safe_connect
  return false unless block

  @object.default_iface = MANAGER_IFACE
  @object.on_signal('ContextChanged', &block)
  true
rescue StandardError => e
  log_error('ContextChanged subscription failed', e)
  false
end

#on_focus_changed {|app_id, title| ... } ⇒ Boolean

Subscribe to FocusChanged signal. SAFETY: Never raises, silently fails if service unavailable.

Yields:

  • (app_id, title)

    called when focus changes

Returns:

  • (Boolean)

    true if subscription succeeded



162
163
164
165
166
167
168
169
170
171
172
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 162

def on_focus_changed(&block)
  return false unless safe_connect
  return false unless block

  @object.default_iface = FOCUS_IFACE
  @object.on_signal('FocusChanged', &block)
  true
rescue StandardError => e
  log_error('FocusChanged subscription failed', e)
  false
end

#service_available?Boolean

Check if the D-Bus service is available. SAFETY: Never raises, returns false on any error.

Returns:

  • (Boolean)


84
85
86
87
88
89
90
91
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 84

def service_available?
  return false unless connect

  @bus.service(BUS_NAME).exists?
rescue StandardError => e
  log_error('Service availability check failed', e)
  false
end

#set_config(key, value) ⇒ Boolean

Update a configuration key via D-Bus. SAFETY: Never raises, returns false on failure.

Parameters:

  • key (String)

    configuration key (e.g. 'default_engine')

  • value (String)

    new value

Returns:

  • (Boolean)

    true if successful



213
214
215
216
217
218
219
220
221
222
223
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 213

def set_config(key, value)
  return false unless safe_connect

  @object.default_iface = MANAGER_IFACE
  result = @object.SetConfig(key, value.to_s)
  output = result.is_a?(Array) ? result.first : result.to_s
  !output.start_with?('Error:')
rescue StandardError => e
  log_error('SetConfig failed', e)
  false
end

#statusHash

Get service status from Manager interface. SAFETY: Never raises, returns safe default hash on error.

Returns:

  • (Hash)

    status dictionary



97
98
99
100
101
102
103
104
105
106
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 97

def status
  return SAFE_STATUS.dup unless safe_connect

  @object.default_iface = MANAGER_IFACE
  result = @object.GetStatus
  result.is_a?(Array) ? result.first : SAFE_STATUS.dup
rescue StandardError => e
  log_error('GetStatus failed', e)
  SAFE_STATUS.merge('error' => e.message)
end

#switch_context(context_name) ⇒ Boolean

Switch AI context. SAFETY: Never raises, returns false on failure.

Parameters:

  • context_name (String)

    name of context to switch to

Returns:

  • (Boolean)

    true if successful



131
132
133
134
135
136
137
138
139
140
# File 'lib/rosett_ai/desktop/dbus_client.rb', line 131

def switch_context(context_name)
  return false unless safe_connect

  @object.default_iface = MANAGER_IFACE
  @object.SwitchContext(context_name)
  true
rescue StandardError => e
  log_error('SwitchContext failed', e)
  false
end