Furious-GUI 0.2.1__tar.gz → 0.2.2__tar.gz

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 (60) hide show
  1. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Action/Routing.py +4 -4
  2. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Utility/Constants.py +5 -0
  3. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Utility/Translator.py +27 -0
  4. Furious-GUI-0.2.2/Furious/Version.py +1 -0
  5. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/ConnectingProgressBar.py +4 -4
  6. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/EditConfiguration.py +4 -3
  7. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/EditRouting.py +264 -114
  8. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/Widget.py +16 -16
  9. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious_GUI.egg-info/PKG-INFO +17 -1
  10. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/PKG-INFO +17 -1
  11. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/README.md +16 -0
  12. Furious-GUI-0.2.1/Furious/Version.py +0 -1
  13. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Action/Connect.py +0 -0
  14. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Action/EditConfiguration.py +0 -0
  15. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Action/Exit.py +0 -0
  16. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Action/Export.py +0 -0
  17. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Action/Import.py +0 -0
  18. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Action/Language.py +0 -0
  19. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Action/Settings.py +0 -0
  20. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Action/__init__.py +0 -0
  21. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Core/Configuration.py +0 -0
  22. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Core/Core.py +0 -0
  23. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Core/Intellisense.py +0 -0
  24. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Core/__init__.py +0 -0
  25. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Data/font/CascadiaMono +0 -0
  26. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Data/font/LICENSE +0 -0
  27. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Data/hysteria/bypass-Iran.acl +0 -0
  28. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Data/hysteria/bypass-mainland-China.acl +0 -0
  29. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Data/hysteria/country.mmdb +0 -0
  30. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Data/xray/geoip.dat +0 -0
  31. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Data/xray/geosite.dat +0 -0
  32. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Data/xray/iran.dat +0 -0
  33. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Gui/Action.py +0 -0
  34. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Gui/Icon.py +0 -0
  35. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Gui/__init__.py +0 -0
  36. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Utility/Process.py +0 -0
  37. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Utility/Proxy.py +0 -0
  38. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Utility/Resources.py +0 -0
  39. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Utility/Settings.py +0 -0
  40. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Utility/StartupOnBoot.py +0 -0
  41. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Utility/Theme.py +0 -0
  42. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Utility/Utility.py +0 -0
  43. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Utility/__init__.py +0 -0
  44. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/Application.py +0 -0
  45. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/AssetViewer.py +0 -0
  46. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/ExportQRCode.py +0 -0
  47. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/IndentSpinBox.py +0 -0
  48. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/LogViewer.py +0 -0
  49. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/SystemTrayIcon.py +0 -0
  50. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/Widget/__init__.py +0 -0
  51. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/__init__.py +0 -0
  52. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious/__main__.py +0 -0
  53. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious_GUI.egg-info/SOURCES.txt +0 -0
  54. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious_GUI.egg-info/dependency_links.txt +0 -0
  55. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious_GUI.egg-info/entry_points.txt +0 -0
  56. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious_GUI.egg-info/requires.txt +0 -0
  57. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/Furious_GUI.egg-info/top_level.txt +0 -0
  58. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/LICENSE +0 -0
  59. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/setup.cfg +0 -0
  60. {Furious-GUI-0.2.1 → Furious-GUI-0.2.2}/setup.py +0 -0
@@ -46,8 +46,8 @@ BUILTIN_ROUTING_TABLE = {
46
46
  ],
47
47
  },
48
48
  Hysteria.name(): {
49
- 'acl': str(DATA_DIR / 'hysteria' / 'bypass-mainland-China.acl'),
50
- 'mmdb': str(DATA_DIR / 'hysteria' / 'country.mmdb'),
49
+ 'acl': (DATA_DIR / 'hysteria' / 'bypass-mainland-China.acl').as_posix(),
50
+ 'mmdb': (DATA_DIR / 'hysteria' / 'country.mmdb').as_posix(),
51
51
  },
52
52
  },
53
53
  'Bypass Iran': {
@@ -91,8 +91,8 @@ BUILTIN_ROUTING_TABLE = {
91
91
  ],
92
92
  },
93
93
  Hysteria.name(): {
94
- 'acl': str(DATA_DIR / 'hysteria' / 'bypass-Iran.acl'),
95
- 'mmdb': str(DATA_DIR / 'hysteria' / 'country.mmdb'),
94
+ 'acl': (DATA_DIR / 'hysteria' / 'bypass-Iran.acl').as_posix(),
95
+ 'mmdb': (DATA_DIR / 'hysteria' / 'country.mmdb').as_posix(),
96
96
  },
97
97
  },
98
98
  'Global': {
@@ -37,3 +37,8 @@ PROXY_SERVER_BYPASS = (
37
37
  '172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;'
38
38
  '172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*'
39
39
  )
40
+
41
+
42
+ class Color:
43
+ LIGHT_RED_ = '#FF7276'
44
+ LIGHT_BLUE = '#43ACED'
@@ -702,6 +702,33 @@ TRANSLATION = {
702
702
  'ZH': '保存路由配置时出错',
703
703
  'TW': '保存路由配置時出錯',
704
704
  },
705
+ f'Edit {XrayCore.name()} Routing Rules': {
706
+ 'ES': f'Editar Reglas de Enrutamiento de {XrayCore.name()}',
707
+ 'ZH': f'编辑{XrayCore.name()}路由规则',
708
+ 'TW': f'編輯{XrayCore.name()}路由規則',
709
+ },
710
+ f'Edit {Hysteria.name()} Routing Rules': {
711
+ 'ES': f'Editar Reglas de Enrutamiento de {Hysteria.name()}',
712
+ 'ZH': f'编辑{Hysteria.name()}路由规则',
713
+ 'TW': f'編輯{Hysteria.name()}路由規則',
714
+ },
715
+ f'{XrayCore.name()} Routing Rules': {
716
+ 'ES': f'Reglas de Enrutamiento de {XrayCore.name()}',
717
+ 'ZH': f'{XrayCore.name()}路由规则',
718
+ 'TW': f'{XrayCore.name()}路由規則',
719
+ },
720
+ f'Note: If acl is empty or does not exist, {APPLICATION_NAME} '
721
+ f'will fall back to proxy all traffic.': {
722
+ 'ES': f'NOTA: Si la ACL está vacía o no existe, '
723
+ f'{APPLICATION_NAME} volverá a utilizar proxy para todo el tráfico.',
724
+ 'ZH': f'注意:如果acl为空或不存在,{APPLICATION_NAME}将回落到代理所有流量。',
725
+ 'TW': f'注意:如果acl為空或不存在,{APPLICATION_NAME}將回落到代理所有流量。',
726
+ },
727
+ f'Note: "Custom" will use acl and mmdb defined in current user configuration.': {
728
+ 'ES': f'NOTA: "Personalizado" utilizará la acl y mmdb definidas en la configuración de usuario actual',
729
+ 'ZH': f'注意:"自定义"将使用当前用户配置中定义的acl和mmdb。',
730
+ 'TW': f'注意:"自定義"將使用當前用戶配置中定義的acl和mmdb。',
731
+ },
705
732
  # Asset Viewer
706
733
  'Asset File': {
707
734
  'ES': 'Activos',
@@ -0,0 +1 @@
1
+ __version__ = '0.2.2'
@@ -1,4 +1,4 @@
1
- from Furious.Utility.Constants import APPLICATION_NAME, GOLDEN_RATIO
1
+ from Furious.Utility.Constants import APPLICATION_NAME, GOLDEN_RATIO, Color
2
2
  from Furious.Utility.Utility import (
3
3
  bootstrapIcon,
4
4
  StateContext,
@@ -31,7 +31,7 @@ class ConnectingProgressBar(Translatable, SupportConnectedCallback, QWidget):
31
31
  # Create a progress bar widget
32
32
  self.progressBar = QProgressBar(self)
33
33
  self.progressBar.setRange(0, 100)
34
- self.progressBar.setStyleSheet(self.getStyleSheet('#43ACED'))
34
+ self.progressBar.setStyleSheet(self.getStyleSheet(Color.LIGHT_BLUE))
35
35
 
36
36
  # create a timer to update the progress bar
37
37
  self.timer = QtCore.QTimer(self)
@@ -64,11 +64,11 @@ class ConnectingProgressBar(Translatable, SupportConnectedCallback, QWidget):
64
64
 
65
65
  def connectedCallback(self):
66
66
  self.setWindowIcon(bootstrapIcon('rocket-takeoff-connected-dark.svg'))
67
- self.progressBar.setStyleSheet(self.getStyleSheet('#F4364C'))
67
+ self.progressBar.setStyleSheet(self.getStyleSheet(Color.LIGHT_RED_))
68
68
 
69
69
  def disconnectedCallback(self):
70
70
  self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
71
- self.progressBar.setStyleSheet(self.getStyleSheet('#43ACED'))
71
+ self.progressBar.setStyleSheet(self.getStyleSheet(Color.LIGHT_BLUE))
72
72
 
73
73
  def retranslate(self):
74
74
  with StateContext(self):
@@ -24,6 +24,7 @@ from Furious.Utility.Constants import (
24
24
  APPLICATION_REPO_NAME,
25
25
  PLATFORM,
26
26
  GOLDEN_RATIO,
27
+ Color,
27
28
  )
28
29
  from Furious.Utility.Utility import (
29
30
  Base64Encoder,
@@ -1088,7 +1089,7 @@ class NormalServerWidget(Translatable, SupportConnectedCallback, TableWidget):
1088
1089
  self.setVerticalHeader(NormalServerVerticalHeader(self))
1089
1090
 
1090
1091
  # Selection
1091
- self.setSelectionColor('#43ACED')
1092
+ self.setSelectionColor(Color.LIGHT_BLUE)
1092
1093
  self.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
1093
1094
  self.setSelectionMode(QTableWidget.SelectionMode.ExtendedSelection)
1094
1095
 
@@ -1591,12 +1592,12 @@ class NormalServerWidget(Translatable, SupportConnectedCallback, TableWidget):
1591
1592
  super().keyPressEvent(event)
1592
1593
 
1593
1594
  def connectedCallback(self):
1594
- self.setSelectionColor('#F4364C')
1595
+ self.setSelectionColor(Color.LIGHT_RED_)
1595
1596
  # Reactivate with possible color
1596
1597
  self.activateItemByIndex(self.activatedItemIndex)
1597
1598
 
1598
1599
  def disconnectedCallback(self):
1599
- self.setSelectionColor('#43ACED')
1600
+ self.setSelectionColor(Color.LIGHT_BLUE)
1600
1601
  # Reactivate with possible color
1601
1602
  self.activateItemByIndex(self.activatedItemIndex)
1602
1603
 
@@ -25,7 +25,13 @@ from Furious.Widget.EditConfiguration import (
25
25
  QuestionDeleteBox,
26
26
  )
27
27
  from Furious.Widget.AssetViewer import AssetViewerWidget
28
- from Furious.Utility.Constants import APP, APPLICATION_NAME, GOLDEN_RATIO, DATA_DIR
28
+ from Furious.Utility.Constants import (
29
+ APP,
30
+ APPLICATION_NAME,
31
+ GOLDEN_RATIO,
32
+ DATA_DIR,
33
+ Color,
34
+ )
29
35
  from Furious.Utility.Utility import (
30
36
  RoutesStorage,
31
37
  StateContext,
@@ -50,6 +56,7 @@ from PySide6.QtWidgets import (
50
56
  QLabel,
51
57
  QLineEdit,
52
58
  QPlainTextEdit,
59
+ QPushButton,
53
60
  QSplitter,
54
61
  QStyledItemDelegate,
55
62
  QTableWidget,
@@ -99,28 +106,6 @@ BUILTIN_ROUTING_TEXT = {
99
106
  ensure_ascii=False,
100
107
  escape_forward_slashes=False,
101
108
  ),
102
- Hysteria.name(): (
103
- f'# Hysteria Routing rules\n'
104
- f'# \n'
105
- f'# This rule is read-only.\n'
106
- f'# \n'
107
- f'# Hysteria will use rule file defined in "acl"\n'
108
- f'# key and database file defined in "mmdb" key.\n'
109
- f'# \n'
110
- f'# "bypass-mainland-China.acl" and "country.mmdb"\n'
111
- f'# are shipped with {APPLICATION_NAME} by default.\n'
112
- f'# Their locations are listed below.\n'
113
- f'# \n'
114
- f'# See more information:\n'
115
- f'# \n'
116
- f'# https://hysteria.network/docs/acl/\n\n'
117
- )
118
- + ujson.dumps(
119
- BUILTIN_ROUTING_TABLE['Bypass Mainland China'][Hysteria.name()],
120
- indent=2,
121
- ensure_ascii=False,
122
- escape_forward_slashes=False,
123
- ),
124
109
  },
125
110
  'Bypass Iran': {
126
111
  XrayCore.name(): (
@@ -154,28 +139,6 @@ BUILTIN_ROUTING_TEXT = {
154
139
  ensure_ascii=False,
155
140
  escape_forward_slashes=False,
156
141
  ),
157
- Hysteria.name(): (
158
- f'# Hysteria Routing rules\n'
159
- f'# \n'
160
- f'# This rule is read-only.\n'
161
- f'# \n'
162
- f'# Hysteria will use rule file defined in "acl"\n'
163
- f'# key and database file defined in "mmdb" key.\n'
164
- f'# \n'
165
- f'# "bypass-Iran.acl" and "country.mmdb" are\n'
166
- f'# shipped with {APPLICATION_NAME} by default.\n'
167
- f'# Their locations are listed below.\n'
168
- f'# \n'
169
- f'# See more information:\n'
170
- f'# \n'
171
- f'# https://hysteria.network/docs/acl/\n\n'
172
- )
173
- + ujson.dumps(
174
- BUILTIN_ROUTING_TABLE['Bypass Iran'][Hysteria.name()],
175
- indent=2,
176
- ensure_ascii=False,
177
- escape_forward_slashes=False,
178
- ),
179
142
  },
180
143
  'Global': {
181
144
  XrayCore.name(): (
@@ -201,20 +164,6 @@ BUILTIN_ROUTING_TEXT = {
201
164
  ensure_ascii=False,
202
165
  escape_forward_slashes=False,
203
166
  ),
204
- Hysteria.name(): (
205
- f'# Hysteria Routing rules\n'
206
- f'# \n'
207
- f'# This rule is read-only.\n'
208
- f'# \n'
209
- f'# If no rule file or mmdb file is provided, {APPLICATION_NAME}\n'
210
- f'# will fall back to proxy all traffic.\n\n'
211
- )
212
- + ujson.dumps(
213
- BUILTIN_ROUTING_TABLE['Global'][Hysteria.name()],
214
- indent=2,
215
- ensure_ascii=False,
216
- escape_forward_slashes=False,
217
- ),
218
167
  },
219
168
  'Custom': {
220
169
  XrayCore.name(): (
@@ -225,13 +174,26 @@ BUILTIN_ROUTING_TEXT = {
225
174
  f'# "Custom" will use RoutingObject defined\n'
226
175
  f'# in current user configuration.\n\n'
227
176
  ),
228
- Hysteria.name(): (
229
- f'# Hysteria Routing rules\n'
230
- f'# \n'
231
- f'# This rule is read-only.\n'
232
- f'# \n'
233
- f'# "Custom" will use rule file and mmdb file\n'
234
- f'# defined in current user configuration.\n\n'
177
+ },
178
+ }
179
+
180
+
181
+ ROUTING_HINT = {
182
+ 'Bypass Mainland China': {
183
+ XrayCore.name(): '',
184
+ },
185
+ 'Bypass Iran': {
186
+ XrayCore.name(): '',
187
+ },
188
+ 'Global': {
189
+ XrayCore.name(): (
190
+ f'Note: If acl is empty or does not exist, {APPLICATION_NAME} '
191
+ f'will fall back to proxy all traffic.'
192
+ ),
193
+ },
194
+ 'Custom': {
195
+ XrayCore.name(): (
196
+ f'Note: "Custom" will use acl and mmdb defined in current user configuration.'
235
197
  ),
236
198
  },
237
199
  }
@@ -252,7 +214,7 @@ DEFAULT_USER_ROUTING = {
252
214
  },
253
215
  Hysteria.name(): {
254
216
  'acl': '',
255
- 'mmdb': str(DATA_DIR / 'hysteria' / 'country.mmdb'),
217
+ 'mmdb': (DATA_DIR / 'hysteria' / 'country.mmdb').as_posix(),
256
218
  },
257
219
  }
258
220
 
@@ -276,30 +238,6 @@ USER_ROUTING_TEXT = {
276
238
  ensure_ascii=False,
277
239
  escape_forward_slashes=False,
278
240
  ),
279
- Hysteria.name(): (
280
- f'# Hysteria Routing rules\n'
281
- f'# \n'
282
- f'# Define your own routing rules here.\n'
283
- f'# A built-in mmdb filepath is defined\n'
284
- f'# for convenience.\n'
285
- f'# \n'
286
- f'# Need any help? You can check those built-in\n'
287
- f'# routing rules shipped with {APPLICATION_NAME} first.\n'
288
- f'# \n'
289
- f'# Note: JSON does not support comments. Any\n'
290
- f'# lines starting with "#" will be discarded\n'
291
- f'# by {APPLICATION_NAME} forever.\n'
292
- f'# \n'
293
- f'# Note: If any one of filepath is given but\n'
294
- f'# does not exist, {APPLICATION_NAME} will fall back to\n'
295
- f'# proxy all traffic.\n\n'
296
- )
297
- + ujson.dumps(
298
- DEFAULT_USER_ROUTING[Hysteria.name()],
299
- indent=2,
300
- ensure_ascii=False,
301
- escape_forward_slashes=False,
302
- ),
303
241
  }
304
242
 
305
243
 
@@ -477,6 +415,65 @@ class ZoomOutAction(Action):
477
415
  editor.zoomOut()
478
416
 
479
417
 
418
+ class EditRoutingAction(Action):
419
+ def __init__(self, *args, **kwargs):
420
+ super().__init__(*args, **kwargs)
421
+
422
+ self.saveConfInfo = SaveConfInfo(icon=MessageBox.Icon.Information)
423
+
424
+ self.RoutesList = self.parent().RoutesList
425
+
426
+ self.routingEditorRef = self.parent().routingEditorRef
427
+ self.routingDialogRef = self.parent().routingDialogRef
428
+
429
+ def triggeredCallback(self, checked):
430
+ if self.textCompare(f'Edit {XrayCore.name()} Routing Rules'):
431
+ for index in self.parent().selectedIndex:
432
+ self.routingEditorRef[index].show()
433
+
434
+ if self.textCompare(f'Edit {Hysteria.name()} Routing Rules'):
435
+ for index in self.parent().selectedIndex:
436
+ dialog = self.routingDialogRef[index]
437
+ choice = dialog.exec()
438
+
439
+ if dialog.isBuiltin:
440
+ # Built-in. Nothing to do
441
+ logger.info(
442
+ f'built-in routing dialog {BUILTIN_ROUTING[index]} called exec. Nothing to do'
443
+ )
444
+
445
+ continue
446
+
447
+ assert index >= len(BUILTIN_ROUTING)
448
+
449
+ parentIndex = index - len(BUILTIN_ROUTING)
450
+
451
+ if choice == QDialog.DialogCode.Accepted.value:
452
+ ruleValue = dialog.ruleValue()
453
+ mmdbValue = dialog.mmdbValue()
454
+
455
+ self.RoutesList[parentIndex][Hysteria.name()] = {
456
+ 'acl': ruleValue,
457
+ 'mmdb': mmdbValue,
458
+ }
459
+
460
+ # Sync it
461
+ RoutesStorage.sync()
462
+
463
+ if parentIndex == routingToIndex() - len(BUILTIN_ROUTING):
464
+ # Activated routing modified
465
+
466
+ if APP().tray.ConnectAction.isConnected():
467
+ questionFastReconnect(self.saveConfInfo)
468
+ else:
469
+ # Restore
470
+
471
+ routingObject = self.RoutesList[parentIndex][Hysteria.name()]
472
+
473
+ dialog.setRuleValue(routingObject.get('acl', ''))
474
+ dialog.setMmdbValue(routingObject.get('mmdb', ''))
475
+
476
+
480
477
  class RoutingTextBrowser(ZoomableTextBrowser):
481
478
  def __init__(self, *args, **kwargs):
482
479
  super().__init__(*args, **kwargs)
@@ -522,8 +519,7 @@ class RoutingEditor(MainWindow):
522
519
  super().__init__(*args, **kwargs)
523
520
 
524
521
  self.isBuiltin = isBuiltin
525
-
526
- self.setWindowTitle(_('Edit Routing'))
522
+ self.title = ''
527
523
 
528
524
  if APP().tray is not None and APP().tray.ConnectAction.isConnected():
529
525
  self.setWindowIcon(bootstrapIcon('rocket-takeoff-connected-dark.svg'))
@@ -535,8 +531,6 @@ class RoutingEditor(MainWindow):
535
531
  icon=MessageBox.Icon.Question, parent=self
536
532
  )
537
533
 
538
- self.title = ''
539
-
540
534
  # Modified flag
541
535
  self.modified = False
542
536
  self.modifiedMark = ' *'
@@ -619,7 +613,7 @@ class RoutingEditor(MainWindow):
619
613
 
620
614
  self.editorRef.append(editor)
621
615
  self.editorLastSavedText.append(text)
622
- self.editorTab.addTab(editor, core)
616
+ self.editorTab.addTab(editor, _(f'{core} Routing Rules'))
623
617
 
624
618
  def markAsModified(self):
625
619
  # Built-in is read-only. Should not be modified
@@ -678,6 +672,116 @@ class RoutingEditor(MainWindow):
678
672
  self.setWindowTitle(_(self.windowTitle()))
679
673
 
680
674
 
675
+ class RoutingDialog(Translatable, SupportConnectedCallback, QDialog):
676
+ def __init__(self, isBuiltin, hint='', *args, **kwargs):
677
+ super().__init__(*args, **kwargs)
678
+
679
+ self.isBuiltin = isBuiltin
680
+
681
+ self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
682
+
683
+ self.ruleText = QLabel(f'{Hysteria.name()} acl:')
684
+
685
+ self.ruleEdit = QLineEdit()
686
+ self.ruleEdit.setFixedWidth(500)
687
+
688
+ self.ruleFile = QPushButton('...')
689
+ self.ruleFile.setFixedWidth(30)
690
+
691
+ self.ruleHBox = QHBoxLayout()
692
+ self.ruleHBox.addWidget(self.ruleEdit)
693
+ self.ruleHBox.addWidget(self.ruleFile)
694
+
695
+ self.mmdbText = QLabel(f'{Hysteria.name()} mmdb:')
696
+
697
+ self.mmdbEdit = QLineEdit()
698
+ self.mmdbEdit.setFixedWidth(500)
699
+
700
+ self.mmdbFile = QPushButton('...')
701
+ self.mmdbFile.setFixedWidth(30)
702
+
703
+ self.mmdbHBox = QHBoxLayout()
704
+ self.mmdbHBox.addWidget(self.mmdbEdit)
705
+ self.mmdbHBox.addWidget(self.mmdbFile)
706
+
707
+ self.ruleFile.clicked.connect(self.handleRuleFileClicked)
708
+ self.mmdbFile.clicked.connect(self.handleMmdbFileClicked)
709
+
710
+ if self.isBuiltin:
711
+ self.ruleEdit.setReadOnly(True)
712
+ self.mmdbEdit.setReadOnly(True)
713
+
714
+ self.dialogBtns = QDialogButtonBox(QtCore.Qt.Orientation.Horizontal)
715
+
716
+ self.dialogBtns.addButton(_('OK'), QDialogButtonBox.ButtonRole.AcceptRole)
717
+ self.dialogBtns.addButton(_('Cancel'), QDialogButtonBox.ButtonRole.RejectRole)
718
+ self.dialogBtns.accepted.connect(self.accept)
719
+ self.dialogBtns.rejected.connect(self.reject)
720
+
721
+ layout = QFormLayout()
722
+
723
+ if hint:
724
+ self.hint = QLabel(hint)
725
+
726
+ layout.addRow(self.hint)
727
+ else:
728
+ self.hint = None
729
+
730
+ layout.addRow(self.ruleText, self.ruleHBox)
731
+ layout.addRow(self.mmdbText, self.mmdbHBox)
732
+ layout.addRow(self.dialogBtns)
733
+ layout.setFormAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
734
+
735
+ self.setLayout(layout)
736
+
737
+ @QtCore.Slot()
738
+ def handleRuleFileClicked(self):
739
+ filename, selectedFilter = QFileDialog.getOpenFileName(
740
+ self.parent(), _('Import File'), filter=_('All files (*)')
741
+ )
742
+
743
+ if not self.isBuiltin and filename:
744
+ self.ruleEdit.setText(filename)
745
+
746
+ @QtCore.Slot()
747
+ def handleMmdbFileClicked(self):
748
+ filename, selectedFilter = QFileDialog.getOpenFileName(
749
+ self.parent(), _('Import File'), filter=_('All files (*)')
750
+ )
751
+
752
+ if not self.isBuiltin and filename:
753
+ self.mmdbEdit.setText(filename)
754
+
755
+ def setRuleValue(self, text):
756
+ self.ruleEdit.setText(text)
757
+
758
+ def setMmdbValue(self, text):
759
+ self.mmdbEdit.setText(text)
760
+
761
+ def ruleValue(self):
762
+ return self.ruleEdit.text()
763
+
764
+ def mmdbValue(self):
765
+ return self.mmdbEdit.text()
766
+
767
+ def connectedCallback(self):
768
+ self.setWindowIcon(bootstrapIcon('rocket-takeoff-connected-dark.svg'))
769
+
770
+ def disconnectedCallback(self):
771
+ self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
772
+
773
+ def retranslate(self):
774
+ with StateContext(self):
775
+ if self.isBuiltin:
776
+ self.setWindowTitle(_(self.windowTitle()))
777
+
778
+ if self.hint is not None:
779
+ self.hint.setText(_(self.hint.text()))
780
+
781
+ for button in self.dialogBtns.buttons():
782
+ button.setText(_(button.text()))
783
+
784
+
681
785
  class EditRoutingTableHorizontalHeader(HeaderView):
682
786
  def __init__(self, *args, **kwargs):
683
787
  super().__init__(QtCore.Qt.Orientation.Horizontal, *args, **kwargs)
@@ -725,7 +829,10 @@ class EditRoutingTableWidget(Translatable, SupportConnectedCallback, TableWidget
725
829
  # Handy reference
726
830
  self.RoutesList = self.editRoutingWidget.RoutesList
727
831
 
832
+ # Currently only has Xray-Core
728
833
  self.routingEditorRef = []
834
+ # Currently only has Hysteria
835
+ self.routingDialogRef = []
729
836
 
730
837
  # Column count
731
838
  self.setColumnCount(len(EditRoutingTableWidget.HEADER_LABEL))
@@ -784,7 +891,7 @@ class EditRoutingTableWidget(Translatable, SupportConnectedCallback, TableWidget
784
891
  self.setVerticalHeader(EditRoutingTableVerticalHeader(self))
785
892
 
786
893
  # Selection
787
- self.setSelectionColor('#43ACED')
894
+ self.setSelectionColor(Color.LIGHT_BLUE)
788
895
  self.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
789
896
  self.setSelectionMode(QTableWidget.SelectionMode.ExtendedSelection)
790
897
 
@@ -794,25 +901,44 @@ class EditRoutingTableWidget(Translatable, SupportConnectedCallback, TableWidget
794
901
  self.setDropIndicatorShown(False)
795
902
  self.setDefaultDropAction(QtCore.Qt.DropAction.IgnoreAction)
796
903
 
904
+ contextMenuActions = [
905
+ EditRoutingAction(_(f'Edit {XrayCore.name()} Routing Rules'), parent=self),
906
+ EditRoutingAction(_(f'Edit {Hysteria.name()} Routing Rules'), parent=self),
907
+ ]
908
+
909
+ self.contextMenu = Menu(*contextMenuActions)
910
+ self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
911
+
797
912
  # Signals
798
- self.itemActivated.connect(self.handleItemActivated)
913
+ self.customContextMenuRequested.connect(self.handleCustomContextMenuRequested)
799
914
 
800
915
  def initTableFromData(self):
801
- for row in range(len(BUILTIN_ROUTING)):
916
+ for row, routing in enumerate(BUILTIN_ROUTING):
802
917
  self.appendDataByColumn(
803
918
  lambda column: EditRoutingTableWidget.BUILTIN_ITEM_GET_FUNC[column](row)
804
919
  )
805
920
 
806
- routingEditor = self.appendRoutingEditor(
807
- self.item(row, 0).text(), isBuiltin=True
808
- )
921
+ text = self.item(row, 0).text()
922
+
923
+ routingEditor = self.appendRoutingEditor(text, isBuiltin=True)
809
924
 
810
- for core in [XrayCore.name(), Hysteria.name()]:
925
+ for core in [XrayCore.name()]:
811
926
  routingEditor.addTabWithData(
812
927
  core,
813
928
  BUILTIN_ROUTING_TEXT[BUILTIN_ROUTING[row]][core],
814
929
  )
815
930
 
931
+ routingDialog = self.appendRoutingDialog(
932
+ text, hint=_(ROUTING_HINT[routing][XrayCore.name()]), isBuiltin=True
933
+ )
934
+
935
+ routingDialog.ruleEdit.setText(
936
+ BUILTIN_ROUTING_TABLE[routing][Hysteria.name()].get('acl', '')
937
+ )
938
+ routingDialog.mmdbEdit.setText(
939
+ BUILTIN_ROUTING_TABLE[routing][Hysteria.name()].get('mmdb', '')
940
+ )
941
+
816
942
  for row in range(
817
943
  len(BUILTIN_ROUTING),
818
944
  len(BUILTIN_ROUTING) + len(self.RoutesList),
@@ -825,11 +951,11 @@ class EditRoutingTableWidget(Translatable, SupportConnectedCallback, TableWidget
825
951
  lambda column: EditRoutingTableWidget.USER_ITEM_GET_FUNC[column](remark)
826
952
  )
827
953
 
828
- routingEditor = self.appendRoutingEditor(
829
- self.item(row, 0).text(), isBuiltin=False
830
- )
954
+ text = self.item(row, 0).text()
831
955
 
832
- for core in [XrayCore.name(), Hysteria.name()]:
956
+ routingEditor = self.appendRoutingEditor(text, isBuiltin=False)
957
+
958
+ for core in [XrayCore.name()]:
833
959
  routingEditor.addTabWithData(
834
960
  core,
835
961
  ujson.dumps(
@@ -840,9 +966,18 @@ class EditRoutingTableWidget(Translatable, SupportConnectedCallback, TableWidget
840
966
  ),
841
967
  )
842
968
 
843
- @QtCore.Slot(QTableWidgetItem)
844
- def handleItemActivated(self, item):
845
- self.routingEditorRef[item.row()].show()
969
+ routingDialog = self.appendRoutingDialog(text, isBuiltin=False)
970
+
971
+ routingDialog.ruleEdit.setText(
972
+ self.RoutesList[routesIndex][Hysteria.name()].get('acl', '')
973
+ )
974
+ routingDialog.mmdbEdit.setText(
975
+ self.RoutesList[routesIndex][Hysteria.name()].get('mmdb', '')
976
+ )
977
+
978
+ @QtCore.Slot(QtCore.QPoint)
979
+ def handleCustomContextMenuRequested(self, point):
980
+ self.contextMenu.exec(self.mapToGlobal(point))
846
981
 
847
982
  def appendRoutingEditor(self, title, isBuiltin=False):
848
983
  routingEditor = RoutingEditor(isBuiltin)
@@ -856,6 +991,14 @@ class EditRoutingTableWidget(Translatable, SupportConnectedCallback, TableWidget
856
991
 
857
992
  return routingEditor
858
993
 
994
+ def appendRoutingDialog(self, title, hint='', isBuiltin=False):
995
+ routingDialog = RoutingDialog(isBuiltin, hint)
996
+ routingDialog.setWindowTitle(title)
997
+
998
+ self.routingDialogRef.append(routingDialog)
999
+
1000
+ return routingDialog
1001
+
859
1002
  def deleteSelectedItem(self):
860
1003
  # Builtin routing cannot be deleted
861
1004
  indexes = list(filter(lambda x: x >= len(BUILTIN_ROUTING), self.selectedIndex))
@@ -890,6 +1033,7 @@ class EditRoutingTableWidget(Translatable, SupportConnectedCallback, TableWidget
890
1033
 
891
1034
  self.removeRow(takedRow)
892
1035
  self.routingEditorRef.pop(takedRow)
1036
+ self.routingDialogRef.pop(takedRow)
893
1037
  self.RoutesList.pop(takedRow - len(BUILTIN_ROUTING))
894
1038
 
895
1039
  # Remove entry from routing menu
@@ -927,12 +1071,12 @@ class EditRoutingTableWidget(Translatable, SupportConnectedCallback, TableWidget
927
1071
  self.setItem(row, column, item)
928
1072
 
929
1073
  def connectedCallback(self):
930
- self.setSelectionColor('#F4364C')
1074
+ self.setSelectionColor(Color.LIGHT_RED_)
931
1075
  # Reactivate with possible color
932
1076
  self.activateItemByIndex(routingToIndex(), activate=True)
933
1077
 
934
1078
  def disconnectedCallback(self):
935
- self.setSelectionColor('#43ACED')
1079
+ self.setSelectionColor(Color.LIGHT_BLUE)
936
1080
  # Reactivate with possible color
937
1081
  self.activateItemByIndex(routingToIndex(), activate=True)
938
1082
 
@@ -1096,12 +1240,19 @@ class EditRoutingWidget(MainWindow):
1096
1240
  routingRemark, isBuiltin=False
1097
1241
  )
1098
1242
 
1099
- for core in [XrayCore.name(), Hysteria.name()]:
1243
+ for core in [XrayCore.name()]:
1100
1244
  routingEditor.addTabWithData(
1101
1245
  core,
1102
1246
  USER_ROUTING_TEXT[core],
1103
1247
  )
1104
1248
 
1249
+ routingDialog = self.editRoutingTableWidget.appendRoutingDialog(
1250
+ routingRemark, isBuiltin=False
1251
+ )
1252
+ routingDialog.mmdbEdit.setText(
1253
+ (DATA_DIR / 'hysteria' / 'country.mmdb').as_posix()
1254
+ )
1255
+
1105
1256
  self.RoutesList.append(
1106
1257
  {'remark': routingRemark, **DEFAULT_USER_ROUTING}
1107
1258
  )
@@ -1114,7 +1265,6 @@ class EditRoutingWidget(MainWindow):
1114
1265
  routingAction.addAction(
1115
1266
  RoutingChildAction(routingRemark, checkable=True, checked=False)
1116
1267
  )
1117
-
1118
1268
  else:
1119
1269
  # Do nothing
1120
1270
  pass
@@ -1,5 +1,5 @@
1
1
  from Furious.Gui.Action import Action, Seperator
2
- from Furious.Utility.Constants import APP
2
+ from Furious.Utility.Constants import APP, Color
3
3
  from Furious.Utility.Utility import (
4
4
  StateContext,
5
5
  SupportConnectedCallback,
@@ -36,17 +36,17 @@ class HeaderView(SupportConnectedCallback, QHeaderView):
36
36
  self.setSectionsClickable(True)
37
37
 
38
38
  self.setStyleSheet(
39
- f'QHeaderView::section:hover {{ background-color: #43ACED; }}'
39
+ f'QHeaderView::section:hover {{ background-color: {Color.LIGHT_BLUE}; }}'
40
40
  )
41
41
 
42
42
  def connectedCallback(self):
43
43
  self.setStyleSheet(
44
- f'QHeaderView::section:hover {{ background-color: #F4364C; }}'
44
+ f'QHeaderView::section:hover {{ background-color: {Color.LIGHT_RED_}; }}'
45
45
  )
46
46
 
47
47
  def disconnectedCallback(self):
48
48
  self.setStyleSheet(
49
- f'QHeaderView::section:hover {{ background-color: #43ACED; }}'
49
+ f'QHeaderView::section:hover {{ background-color: {Color.LIGHT_BLUE}; }}'
50
50
  )
51
51
 
52
52
 
@@ -54,7 +54,7 @@ class ListWidget(SupportConnectedCallback, QListWidget):
54
54
  def __init__(self, *args, **kwargs):
55
55
  super().__init__(*args, **kwargs)
56
56
 
57
- self.setSelectionColor('#43ACED')
57
+ self.setSelectionColor(Color.LIGHT_BLUE)
58
58
 
59
59
  def setSelectionColor(self, color):
60
60
  self.setStyleSheet(
@@ -72,10 +72,10 @@ class ListWidget(SupportConnectedCallback, QListWidget):
72
72
  return sorted(list(set(index.row() for index in self.selectedIndexes())))
73
73
 
74
74
  def connectedCallback(self):
75
- self.setSelectionColor('#F4364C')
75
+ self.setSelectionColor(Color.LIGHT_RED_)
76
76
 
77
77
  def disconnectedCallback(self):
78
- self.setSelectionColor('#43ACED')
78
+ self.setSelectionColor(Color.LIGHT_BLUE)
79
79
 
80
80
 
81
81
  class MainWindow(Translatable, SupportConnectedCallback, NeedSyncSettings, QMainWindow):
@@ -118,7 +118,7 @@ class Menu(Translatable, SupportConnectedCallback, QMenu):
118
118
 
119
119
  self.addAction(action)
120
120
 
121
- self.setStyleSheet(self.getStyleSheet('#43ACED'))
121
+ self.setStyleSheet(self.getStyleSheet(Color.LIGHT_BLUE))
122
122
 
123
123
  @staticmethod
124
124
  def getStyleSheet(color):
@@ -133,10 +133,10 @@ class Menu(Translatable, SupportConnectedCallback, QMenu):
133
133
  )
134
134
 
135
135
  def connectedCallback(self):
136
- self.setStyleSheet(self.getStyleSheet('#F4364C'))
136
+ self.setStyleSheet(self.getStyleSheet(Color.LIGHT_RED_))
137
137
 
138
138
  def disconnectedCallback(self):
139
- self.setStyleSheet(self.getStyleSheet('#43ACED'))
139
+ self.setStyleSheet(self.getStyleSheet(Color.LIGHT_BLUE))
140
140
 
141
141
  def retranslate(self):
142
142
  with StateContext(self):
@@ -147,7 +147,7 @@ class MenuBar(SupportConnectedCallback, QMenuBar):
147
147
  def __init__(self, *args, **kwargs):
148
148
  super().__init__(*args, **kwargs)
149
149
 
150
- self.setSelectionColor('#43ACED')
150
+ self.setSelectionColor(Color.LIGHT_BLUE)
151
151
 
152
152
  def setSelectionColor(self, color):
153
153
  self.setStyleSheet(
@@ -161,10 +161,10 @@ class MenuBar(SupportConnectedCallback, QMenuBar):
161
161
  )
162
162
 
163
163
  def connectedCallback(self):
164
- self.setSelectionColor('#F4364C')
164
+ self.setSelectionColor(Color.LIGHT_RED_)
165
165
 
166
166
  def disconnectedCallback(self):
167
- self.setSelectionColor('#43ACED')
167
+ self.setSelectionColor(Color.LIGHT_BLUE)
168
168
 
169
169
 
170
170
  class MessageBox(Translatable, SupportConnectedCallback, QMessageBox):
@@ -263,12 +263,12 @@ class TableWidget(QTableWidget):
263
263
 
264
264
  if APP().tray is None:
265
265
  # Initializing
266
- item.setForeground(QColor('#43ACED'))
266
+ item.setForeground(QColor(Color.LIGHT_BLUE))
267
267
  else:
268
268
  if APP().tray.ConnectAction.isConnected():
269
- item.setForeground(QColor('#F4364C'))
269
+ item.setForeground(QColor(Color.LIGHT_RED_))
270
270
  else:
271
- item.setForeground(QColor('#43ACED'))
271
+ item.setForeground(QColor(Color.LIGHT_BLUE))
272
272
  else:
273
273
  for column in range(self.columnCount()):
274
274
  item = self.item(int(index), column)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Furious-GUI
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A PySide6-based cross platform GUI client that launches your beloved GFW to outer space.
5
5
  Home-page: https://github.com/LorenEteval/Furious
6
6
  Author: Loren Eteval
@@ -23,6 +23,8 @@ License-File: LICENSE
23
23
 
24
24
  # Furious
25
25
 
26
+ [![Build Furious](https://github.com/LorenEteval/Furious/actions/workflows/wheels.yml/badge.svg?branch=main)](https://github.com/LorenEteval/Furious/actions/workflows/wheels.yml)
27
+
26
28
  A PySide6-based cross platform GUI client that launches your beloved GFW to outer space.
27
29
  Support [Xray-core](https://github.com/XTLS/Xray-core)
28
30
  and [hysteria](https://github.com/apernet/hysteria).
@@ -137,6 +139,20 @@ python -m Furious
137
139
 
138
140
  > Note: Furious will ignore current startup on boot request if it's lauched from source.
139
141
 
142
+ ## Build Status
143
+
144
+ See the build result in [github actions](https://github.com/LorenEteval/Furious/actions).
145
+
146
+ | Platform | Python 3.8-Python 3.11 |
147
+ |--------------|:----------------------:|
148
+ | ubuntu 20.04 | :heavy_check_mark: |
149
+ | ubuntu 22.04 | :heavy_check_mark: |
150
+ | windows-2019 | :heavy_check_mark: |
151
+ | windows-2022 | :heavy_check_mark: |
152
+ | macos-11 | :heavy_check_mark: |
153
+ | macos-12 | :heavy_check_mark: |
154
+ | macos-13 | :heavy_check_mark: |
155
+
140
156
  ## Core Installation Script
141
157
 
142
158
  Below are some one-click/automatic installation script that's been tested to work in Furious.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Furious-GUI
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A PySide6-based cross platform GUI client that launches your beloved GFW to outer space.
5
5
  Home-page: https://github.com/LorenEteval/Furious
6
6
  Author: Loren Eteval
@@ -23,6 +23,8 @@ License-File: LICENSE
23
23
 
24
24
  # Furious
25
25
 
26
+ [![Build Furious](https://github.com/LorenEteval/Furious/actions/workflows/wheels.yml/badge.svg?branch=main)](https://github.com/LorenEteval/Furious/actions/workflows/wheels.yml)
27
+
26
28
  A PySide6-based cross platform GUI client that launches your beloved GFW to outer space.
27
29
  Support [Xray-core](https://github.com/XTLS/Xray-core)
28
30
  and [hysteria](https://github.com/apernet/hysteria).
@@ -137,6 +139,20 @@ python -m Furious
137
139
 
138
140
  > Note: Furious will ignore current startup on boot request if it's lauched from source.
139
141
 
142
+ ## Build Status
143
+
144
+ See the build result in [github actions](https://github.com/LorenEteval/Furious/actions).
145
+
146
+ | Platform | Python 3.8-Python 3.11 |
147
+ |--------------|:----------------------:|
148
+ | ubuntu 20.04 | :heavy_check_mark: |
149
+ | ubuntu 22.04 | :heavy_check_mark: |
150
+ | windows-2019 | :heavy_check_mark: |
151
+ | windows-2022 | :heavy_check_mark: |
152
+ | macos-11 | :heavy_check_mark: |
153
+ | macos-12 | :heavy_check_mark: |
154
+ | macos-13 | :heavy_check_mark: |
155
+
140
156
  ## Core Installation Script
141
157
 
142
158
  Below are some one-click/automatic installation script that's been tested to work in Furious.
@@ -1,5 +1,7 @@
1
1
  # Furious
2
2
 
3
+ [![Build Furious](https://github.com/LorenEteval/Furious/actions/workflows/wheels.yml/badge.svg?branch=main)](https://github.com/LorenEteval/Furious/actions/workflows/wheels.yml)
4
+
3
5
  A PySide6-based cross platform GUI client that launches your beloved GFW to outer space.
4
6
  Support [Xray-core](https://github.com/XTLS/Xray-core)
5
7
  and [hysteria](https://github.com/apernet/hysteria).
@@ -114,6 +116,20 @@ python -m Furious
114
116
 
115
117
  > Note: Furious will ignore current startup on boot request if it's lauched from source.
116
118
 
119
+ ## Build Status
120
+
121
+ See the build result in [github actions](https://github.com/LorenEteval/Furious/actions).
122
+
123
+ | Platform | Python 3.8-Python 3.11 |
124
+ |--------------|:----------------------:|
125
+ | ubuntu 20.04 | :heavy_check_mark: |
126
+ | ubuntu 22.04 | :heavy_check_mark: |
127
+ | windows-2019 | :heavy_check_mark: |
128
+ | windows-2022 | :heavy_check_mark: |
129
+ | macos-11 | :heavy_check_mark: |
130
+ | macos-12 | :heavy_check_mark: |
131
+ | macos-13 | :heavy_check_mark: |
132
+
117
133
  ## Core Installation Script
118
134
 
119
135
  Below are some one-click/automatic installation script that's been tested to work in Furious.
@@ -1 +0,0 @@
1
- __version__ = '0.2.1'
File without changes
File without changes
File without changes