├── .gitignore
├── .pylintrc
├── .vscode
└── settings.json
├── LICENSE
├── Makefile
├── README.md
├── __pycache__
└── find_entries.cpython-310.pyc
├── docs
└── preferences.json.md
├── grub-editor.desktop
├── grub-editor.png
├── grub-editor.py
├── grubEditor
├── __init__.py
├── __main__.py
├── core.py
├── libs
│ ├── __init__.py
│ ├── find_entries.py
│ ├── qt_functools.py
│ └── worker.py
├── locations.py
├── main.py
├── ui
│ ├── chroot.ui
│ ├── chroot_after.ui
│ ├── chroot_loading.ui
│ ├── create_snapshot_dialog.ui
│ ├── dialog.ui
│ ├── issues.ui
│ ├── main.ui
│ ├── main1.ui
│ ├── progress.ui
│ ├── set_recommendations.ui
│ ├── snapshots_.ui
│ └── treeWidgetTest.ui
└── widgets
│ ├── __init__.py
│ ├── dialog.py
│ ├── elided_label.py
│ ├── error_dialog.py
│ ├── loading_bar.py
│ ├── progress.py
│ ├── ui
│ ├── error_dialog.ui
│ └── view_snapshot.ui
│ └── view_mode_popup.py
├── screenshots
├── grub-editor0.png
├── grub-editor1.png
├── grub-editor2.png
├── light-screenshot0.png
└── light-screenshot1.png
├── tests
├── pytest.ini
├── test_edit_configurations.py
├── test_fix_kernel_version.py
├── test_get_set_value.py
├── test_grubcfg_error.py
├── test_invalid_default_entry.py
├── test_output_widget.py
├── test_progress.py
├── test_quotes_values.py
├── test_reinstall_grub_package.py
├── test_remove_value.py
├── test_snapshots.py
├── test_widget_dialog.py
└── tools.py
└── todo.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | temp*
2 | temp1.py
3 | temp2.py
4 | temp3.py
5 | temp4.py
6 | temp5.py
7 | tempe.py
8 | temp7.py
9 | test_json.py
10 | temp8.py
11 | PKGBUILD
12 | *.pyc
13 | __pycache__
14 | .qt_for_python/
15 | pytest.ini
16 | .vscode/
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 | [MAIN]
2 |
3 | # Analyse import fallback blocks. This can be used to support both Python 2 and
4 | # 3 compatible code, which means that the block might have code that exists
5 | # only in one or another interpreter, leading to false positives when analysed.
6 | analyse-fallback-blocks=no
7 |
8 | # Load and enable all available extensions. Use --list-extensions to see a list
9 | # all available extensions.
10 | #enable-all-extensions=
11 |
12 | # In error mode, messages with a category besides ERROR or FATAL are
13 | # suppressed, and no reports are done by default. Error mode is compatible with
14 | # disabling specific errors.
15 | #errors-only=
16 |
17 | # Always return a 0 (non-error) status code, even if lint errors are found.
18 | # This is primarily useful in continuous integration scripts.
19 | #exit-zero=
20 |
21 | # A comma-separated list of package or module names from where C extensions may
22 | # be loaded. Extensions are loading into the active Python interpreter and may
23 | # run arbitrary code.
24 | extension-pkg-allow-list=
25 |
26 | # A comma-separated list of package or module names from where C extensions may
27 | # be loaded. Extensions are loading into the active Python interpreter and may
28 | # run arbitrary code. (This is an alternative name to extension-pkg-allow-list
29 | # for backward compatibility.)
30 | extension-pkg-whitelist=
31 |
32 | # Return non-zero exit code if any of these messages/categories are detected,
33 | # even if score is above --fail-under value. Syntax same as enable. Messages
34 | # specified are enabled, while categories only check already-enabled messages.
35 | fail-on=
36 |
37 | # Specify a score threshold to be exceeded before program exits with error.
38 | fail-under=10
39 |
40 | # Interpret the stdin as a python script, whose filename needs to be passed as
41 | # the module_or_package argument.
42 | #from-stdin=
43 |
44 | # Files or directories to be skipped. They should be base names, not paths.
45 | ignore=CVS
46 |
47 | # Add files or directories matching the regex patterns to the ignore-list. The
48 | # regex matches against paths and can be in Posix or Windows format.
49 | ignore-paths=
50 |
51 | # Files or directories matching the regex patterns are skipped. The regex
52 | # matches against base names, not paths. The default value ignores Emacs file
53 | # locks
54 | ignore-patterns=^\.#
55 |
56 | # List of module names for which member attributes should not be checked
57 | # (useful for modules/projects where namespaces are manipulated during runtime
58 | # and thus existing member attributes cannot be deduced by static analysis). It
59 | # supports qualified module names, as well as Unix pattern matching.
60 | ignored-modules=
61 |
62 | # Python code to execute, usually for sys.path manipulation such as
63 | # pygtk.require().
64 | #init-hook=
65 |
66 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
67 | # number of processors available to use.
68 | jobs=1
69 |
70 | # Control the amount of potential inferred values when inferring a single
71 | # object. This can help the performance when dealing with large functions or
72 | # complex, nested conditions.
73 | limit-inference-results=100
74 |
75 | # List of plugins (as comma separated values of python module names) to load,
76 | # usually to register additional checkers.
77 | load-plugins=
78 |
79 | # Pickle collected data for later comparisons.
80 | persistent=yes
81 |
82 | # Minimum Python version to use for version dependent checks. Will default to
83 | # the version used to run pylint.
84 | py-version=3.10
85 |
86 | # Discover python modules and packages in the file system subtree.
87 | recursive=no
88 |
89 | # When enabled, pylint would attempt to guess common misconfiguration and emit
90 | # user-friendly hints instead of false-positive error messages.
91 | suggestion-mode=yes
92 |
93 | # Allow loading of arbitrary C extensions. Extensions are imported into the
94 | # active Python interpreter and may run arbitrary code.
95 | unsafe-load-any-extension=no
96 |
97 | # In verbose mode, extra non-checker-related info will be displayed.
98 | #verbose=
99 |
100 |
101 | [REPORTS]
102 |
103 | # Python expression which should return a score less than or equal to 10. You
104 | # have access to the variables 'fatal', 'error', 'warning', 'refactor',
105 | # 'convention', and 'info' which contain the number of messages in each
106 | # category, as well as 'statement' which is the total number of statements
107 | # analyzed. This score is used by the global evaluation report (RP0004).
108 | evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
109 |
110 | # Template used to display messages. This is a python new-style format string
111 | # used to format the message information. See doc for all details.
112 | msg-template=
113 |
114 | # Set the output format. Available formats are text, parseable, colorized, json
115 | # and msvs (visual studio). You can also give a reporter class, e.g.
116 | # mypackage.mymodule.MyReporterClass.
117 | #output-format=
118 |
119 | # Tells whether to display a full report or only the messages.
120 | reports=no
121 |
122 | # Activate the evaluation score.
123 | score=yes
124 |
125 |
126 | [MESSAGES CONTROL]
127 |
128 | # Only show warnings with the listed confidence levels. Leave empty to show
129 | # all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
130 | # UNDEFINED.
131 | confidence=HIGH,
132 | CONTROL_FLOW,
133 | INFERENCE,
134 | INFERENCE_FAILURE,
135 | UNDEFINED
136 |
137 | # Disable the message, report, category or checker with the given id(s). You
138 | # can either give multiple identifiers separated by comma (,) or put this
139 | # option multiple times (only on the command line, not in the configuration
140 | # file where it should appear only once). You can also use "--disable=all" to
141 | # disable everything first and then re-enable specific checks. For example, if
142 | # you want to run only the similarities checker, you can use "--disable=all
143 | # --enable=similarities". If you want to run only the classes checker, but have
144 | # no Warning level messages displayed, use "--disable=all --enable=classes
145 | # --disable=W".
146 | disable=raw-checker-failed,
147 | bad-inline-option,
148 | locally-disabled,
149 | file-ignored,
150 | suppressed-message,
151 | useless-suppression,
152 | deprecated-pragma,
153 | use-symbolic-message-instead,
154 | no-name-in-module,
155 | unused-import,
156 | subprocess-run-check,
157 | unspecified-encoding,
158 | trailing-whitespace,
159 | c-extension-no-member,
160 | invalid-name,
161 | wrong-import-position,
162 | import-error,
163 | attribute-defined-outside-init,
164 | unused-wildcard-import,
165 | wildcard-import,
166 | missing-function-docstring,
167 | missing-class-docstring,
168 | global-at-module-level
169 |
170 | ; disable=all
171 |
172 | # Enable the message, report, category or checker with the given id(s). You can
173 | # either give multiple identifier separated by comma (,) or put this option
174 | # multiple time (only on the command line, not in the configuration file where
175 | # it should appear only once). See also the "--disable" option for examples.
176 | #enable=
177 |
178 |
179 | [BASIC]
180 |
181 | # Naming style matching correct argument names.
182 | argument-naming-style=snake_case
183 |
184 | # Regular expression matching correct argument names. Overrides argument-
185 | # naming-style. If left empty, argument names will be checked with the set
186 | # naming style.
187 | #argument-rgx=
188 |
189 | # Naming style matching correct attribute names.
190 | attr-naming-style=snake_case
191 |
192 | # Regular expression matching correct attribute names. Overrides attr-naming-
193 | # style. If left empty, attribute names will be checked with the set naming
194 | # style.
195 | #attr-rgx=
196 |
197 | # Bad variable names which should always be refused, separated by a comma.
198 | bad-names=foo,
199 | bar,
200 | baz,
201 | toto,
202 | tutu,
203 | tata
204 |
205 | # Bad variable names regexes, separated by a comma. If names match any regex,
206 | # they will always be refused
207 | bad-names-rgxs=
208 |
209 | # Naming style matching correct class attribute names.
210 | class-attribute-naming-style=any
211 |
212 | # Regular expression matching correct class attribute names. Overrides class-
213 | # attribute-naming-style. If left empty, class attribute names will be checked
214 | # with the set naming style.
215 | #class-attribute-rgx=
216 |
217 | # Naming style matching correct class constant names.
218 | class-const-naming-style=UPPER_CASE
219 |
220 | # Regular expression matching correct class constant names. Overrides class-
221 | # const-naming-style. If left empty, class constant names will be checked with
222 | # the set naming style.
223 | #class-const-rgx=
224 |
225 | # Naming style matching correct class names.
226 | class-naming-style=PascalCase
227 |
228 | # Regular expression matching correct class names. Overrides class-naming-
229 | # style. If left empty, class names will be checked with the set naming style.
230 | #class-rgx=
231 |
232 | # Naming style matching correct constant names.
233 | const-naming-style=UPPER_CASE
234 |
235 | # Regular expression matching correct constant names. Overrides const-naming-
236 | # style. If left empty, constant names will be checked with the set naming
237 | # style.
238 | #const-rgx=
239 |
240 | # Minimum line length for functions/classes that require docstrings, shorter
241 | # ones are exempt.
242 | docstring-min-length=-1
243 |
244 | # Naming style matching correct function names.
245 | function-naming-style=snake_case
246 |
247 | # Regular expression matching correct function names. Overrides function-
248 | # naming-style. If left empty, function names will be checked with the set
249 | # naming style.
250 | #function-rgx=
251 |
252 | # Good variable names which should always be accepted, separated by a comma.
253 | good-names=i,
254 | j,
255 | k,
256 | ex,
257 | Run,
258 | _
259 |
260 | # Good variable names regexes, separated by a comma. If names match any regex,
261 | # they will always be accepted
262 | good-names-rgxs=
263 |
264 | # Include a hint for the correct naming format with invalid-name.
265 | include-naming-hint=no
266 |
267 | # Naming style matching correct inline iteration names.
268 | inlinevar-naming-style=any
269 |
270 | # Regular expression matching correct inline iteration names. Overrides
271 | # inlinevar-naming-style. If left empty, inline iteration names will be checked
272 | # with the set naming style.
273 | #inlinevar-rgx=
274 |
275 | # Naming style matching correct method names.
276 | method-naming-style=snake_case
277 |
278 | # Regular expression matching correct method names. Overrides method-naming-
279 | # style. If left empty, method names will be checked with the set naming style.
280 | #method-rgx=
281 |
282 | # Naming style matching correct module names.
283 | module-naming-style=snake_case
284 |
285 | # Regular expression matching correct module names. Overrides module-naming-
286 | # style. If left empty, module names will be checked with the set naming style.
287 | #module-rgx=
288 |
289 | # Colon-delimited sets of names that determine each other's naming style when
290 | # the name regexes allow several styles.
291 | name-group=
292 |
293 | # Regular expression which should only match function or class names that do
294 | # not require a docstring.
295 | no-docstring-rgx=^_
296 |
297 | # List of decorators that produce properties, such as abc.abstractproperty. Add
298 | # to this list to register other decorators that produce valid properties.
299 | # These decorators are taken in consideration only for invalid-name.
300 | property-classes=abc.abstractproperty
301 |
302 | # Regular expression matching correct type variable names. If left empty, type
303 | # variable names will be checked with the set naming style.
304 | #typevar-rgx=
305 |
306 | # Naming style matching correct variable names.
307 | variable-naming-style=snake_case
308 |
309 | # Regular expression matching correct variable names. Overrides variable-
310 | # naming-style. If left empty, variable names will be checked with the set
311 | # naming style.
312 | #variable-rgx=
313 |
314 |
315 | [VARIABLES]
316 |
317 | # List of additional names supposed to be defined in builtins. Remember that
318 | # you should avoid defining new builtins when possible.
319 | additional-builtins=
320 |
321 | # Tells whether unused global variables should be treated as a violation.
322 | allow-global-unused-variables=yes
323 |
324 | # List of names allowed to shadow builtins
325 | allowed-redefined-builtins=
326 |
327 | # List of strings which can identify a callback function by name. A callback
328 | # name must start or end with one of those strings.
329 | callbacks=cb_,
330 | _cb
331 |
332 | # A regular expression matching the name of dummy variables (i.e. expected to
333 | # not be used).
334 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
335 |
336 | # Argument names that match this expression will be ignored. Default to name
337 | # with leading underscore.
338 | ignored-argument-names=_.*|^ignored_|^unused_
339 |
340 | # Tells whether we should check for unused import in __init__ files.
341 | init-import=no
342 |
343 | # List of qualified module names which can have objects that can redefine
344 | # builtins.
345 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
346 |
347 |
348 | [MISCELLANEOUS]
349 |
350 | # List of note tags to take in consideration, separated by a comma.
351 | notes=FIXME,
352 | XXX,
353 | TODO
354 |
355 | # Regular expression of note tags to take in consideration.
356 | notes-rgx=
357 |
358 |
359 | [CLASSES]
360 |
361 | # Warn about protected attribute access inside special methods
362 | check-protected-access-in-special-methods=no
363 |
364 | # List of method names used to declare (i.e. assign) instance attributes.
365 | defining-attr-methods=__init__,
366 | __new__,
367 | setUp,
368 | __post_init__
369 |
370 | # List of member names, which should be excluded from the protected access
371 | # warning.
372 | exclude-protected=_asdict,
373 | _fields,
374 | _replace,
375 | _source,
376 | _make
377 |
378 | # List of valid names for the first argument in a class method.
379 | valid-classmethod-first-arg=cls
380 |
381 | # List of valid names for the first argument in a metaclass class method.
382 | valid-metaclass-classmethod-first-arg=cls
383 |
384 |
385 | [EXCEPTIONS]
386 |
387 | # Exceptions that will emit a warning when caught.
388 | overgeneral-exceptions=BaseException,
389 | Exception
390 |
391 |
392 | [FORMAT]
393 |
394 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
395 | expected-line-ending-format=
396 |
397 | # Regexp for a line that is allowed to be longer than the limit.
398 | ignore-long-lines=^\s*(# )??$
399 |
400 | # Number of spaces of indent required inside a hanging or continued line.
401 | indent-after-paren=4
402 |
403 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
404 | # tab).
405 | indent-string=' '
406 |
407 | # Maximum number of characters on a single line.
408 | max-line-length=100
409 |
410 | # Maximum number of lines in a module.
411 | max-module-lines=1000
412 |
413 | # Allow the body of a class to be on the same line as the declaration if body
414 | # contains single statement.
415 | single-line-class-stmt=no
416 |
417 | # Allow the body of an if to be on the same line as the test if there is no
418 | # else.
419 | single-line-if-stmt=no
420 |
421 |
422 | [SPELLING]
423 |
424 | # Limits count of emitted suggestions for spelling mistakes.
425 | max-spelling-suggestions=4
426 |
427 | # Spelling dictionary name. Available dictionaries: none. To make it work,
428 | # install the 'python-enchant' package.
429 | spelling-dict=
430 |
431 | # List of comma separated words that should be considered directives if they
432 | # appear at the beginning of a comment and should not be checked.
433 | spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
434 |
435 | # List of comma separated words that should not be checked.
436 | spelling-ignore-words=
437 |
438 | # A path to a file that contains the private dictionary; one word per line.
439 | spelling-private-dict-file=
440 |
441 | # Tells whether to store unknown words to the private dictionary (see the
442 | # --spelling-private-dict-file option) instead of raising a message.
443 | spelling-store-unknown-words=no
444 |
445 |
446 | [LOGGING]
447 |
448 | # The type of string formatting that logging methods do. `old` means using %
449 | # formatting, `new` is for `{}` formatting.
450 | logging-format-style=old
451 |
452 | # Logging modules to check that the string format arguments are in logging
453 | # function parameter format.
454 | logging-modules=logging
455 |
456 |
457 | [STRING]
458 |
459 | # This flag controls whether inconsistent-quotes generates a warning when the
460 | # character used as a quote delimiter is used inconsistently within a module.
461 | check-quote-consistency=no
462 |
463 | # This flag controls whether the implicit-str-concat should generate a warning
464 | # on implicit string concatenation in sequences defined over several lines.
465 | check-str-concat-over-line-jumps=no
466 |
467 |
468 | [SIMILARITIES]
469 |
470 | # Comments are removed from the similarity computation
471 | ignore-comments=yes
472 |
473 | # Docstrings are removed from the similarity computation
474 | ignore-docstrings=yes
475 |
476 | # Imports are removed from the similarity computation
477 | ignore-imports=yes
478 |
479 | # Signatures are removed from the similarity computation
480 | ignore-signatures=yes
481 |
482 | # Minimum lines number of a similarity.
483 | min-similarity-lines=4
484 |
485 |
486 | [REFACTORING]
487 |
488 | # Maximum number of nested blocks for function / method body
489 | max-nested-blocks=5
490 |
491 | # Complete name of functions that never returns. When checking for
492 | # inconsistent-return-statements if a never returning function is called then
493 | # it will be considered as an explicit return statement and no message will be
494 | # printed.
495 | never-returning-functions=sys.exit,argparse.parse_error
496 |
497 |
498 | [IMPORTS]
499 |
500 | # List of modules that can be imported at any level, not just the top level
501 | # one.
502 | allow-any-import-level=
503 |
504 | # Allow wildcard imports from modules that define __all__.
505 | allow-wildcard-with-all=no
506 |
507 | # Deprecated modules which should not be used, separated by a comma.
508 | deprecated-modules=
509 |
510 | # Output a graph (.gv or any supported image format) of external dependencies
511 | # to the given file (report RP0402 must not be disabled).
512 | ext-import-graph=
513 |
514 | # Output a graph (.gv or any supported image format) of all (i.e. internal and
515 | # external) dependencies to the given file (report RP0402 must not be
516 | # disabled).
517 | import-graph=
518 |
519 | # Output a graph (.gv or any supported image format) of internal dependencies
520 | # to the given file (report RP0402 must not be disabled).
521 | int-import-graph=
522 |
523 | # Force import order to recognize a module as part of the standard
524 | # compatibility libraries.
525 | known-standard-library=
526 |
527 | # Force import order to recognize a module as part of a third party library.
528 | known-third-party=enchant
529 |
530 | # Couples of modules and preferred modules, separated by a comma.
531 | preferred-modules=
532 |
533 |
534 | [DESIGN]
535 |
536 | # List of regular expressions of class ancestor names to ignore when counting
537 | # public methods (see R0903)
538 | exclude-too-few-public-methods=
539 |
540 | # List of qualified class names to ignore when counting class parents (see
541 | # R0901)
542 | ignored-parents=
543 |
544 | # Maximum number of arguments for function / method.
545 | max-args=5
546 |
547 | # Maximum number of attributes for a class (see R0902).
548 | max-attributes=7
549 |
550 | # Maximum number of boolean expressions in an if statement (see R0916).
551 | max-bool-expr=5
552 |
553 | # Maximum number of branch for function / method body.
554 | max-branches=12
555 |
556 | # Maximum number of locals for function / method body.
557 | max-locals=15
558 |
559 | # Maximum number of parents for a class (see R0901).
560 | max-parents=7
561 |
562 | # Maximum number of public methods for a class (see R0904).
563 | max-public-methods=20
564 |
565 | # Maximum number of return / yield for function / method body.
566 | max-returns=6
567 |
568 | # Maximum number of statements in function / method body.
569 | max-statements=50
570 |
571 | # Minimum number of public methods for a class (see R0903).
572 | min-public-methods=2
573 |
574 |
575 | [TYPECHECK]
576 |
577 | # List of decorators that produce context managers, such as
578 | # contextlib.contextmanager. Add to this list to register other decorators that
579 | # produce valid context managers.
580 | contextmanager-decorators=contextlib.contextmanager
581 |
582 | # List of members which are set dynamically and missed by pylint inference
583 | # system, and so shouldn't trigger E1101 when accessed. Python regular
584 | # expressions are accepted.
585 | generated-members=
586 |
587 | # Tells whether to warn about missing members when the owner of the attribute
588 | # is inferred to be None.
589 | ignore-none=yes
590 |
591 | # This flag controls whether pylint should warn about no-member and similar
592 | # checks whenever an opaque object is returned when inferring. The inference
593 | # can return multiple potential results while evaluating a Python object, but
594 | # some branches might not be evaluated, which results in partial inference. In
595 | # that case, it might be useful to still emit no-member and other checks for
596 | # the rest of the inferred objects.
597 | ignore-on-opaque-inference=yes
598 |
599 | # List of symbolic message names to ignore for Mixin members.
600 | ignored-checks-for-mixins=no-member,
601 | not-async-context-manager,
602 | not-context-manager,
603 | attribute-defined-outside-init
604 |
605 | # List of class names for which member attributes should not be checked (useful
606 | # for classes with dynamically set attributes). This supports the use of
607 | # qualified names.
608 | ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
609 |
610 | # Show a hint with possible names when a member name was not found. The aspect
611 | # of finding the hint is based on edit distance.
612 | missing-member-hint=yes
613 |
614 | # The minimum edit distance a name should have in order to be considered a
615 | # similar match for a missing member name.
616 | missing-member-hint-distance=1
617 |
618 | # The total number of similar names that should be taken in consideration when
619 | # showing a hint for a missing member.
620 | missing-member-max-choices=1
621 |
622 | # Regex pattern to define which classes are considered mixins.
623 | mixin-class-rgx=.*[Mm]ixin
624 |
625 | # List of decorators that change the signature of a decorated function.
626 | signature-mutators=
627 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.testing.pytestArgs": [
3 | "tests"
4 | ],
5 | "python.testing.unittestEnabled": false,
6 | "python.testing.pytestEnabled": true
7 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2022 Scott Chacon and others
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | DESKTOP_FILE=grub-editor.desktop
2 | INSTALL_PATH=$(PKG_DIR)/opt/grub-editor
3 | DESKTOP_PATH=$(PKG_DIR)/usr/share/applications
4 | LICENSE_PATH=$(PKG_DIR)/usr/share/licenses/grub-editor
5 | ICON_PATH=$(PKG_DIR)/usr/share/pixmaps
6 | install:
7 | find . -type f -exec install -Dm 755 "{}" "$(INSTALL_PATH)/{}" \;
8 | install $(DESKTOP_FILE) -D $(DESKTOP_PATH)/$(DESKTOP_FILE)
9 | # install README -D $(DOCPATH)/README
10 | # install $(DOC)/CHANGES -D $(DOCPATH)/CHANGES
11 | install LICENSE -D $(LICENSE_PATH)/LICENSE
12 | install -D grub-editor.png $(ICON_PATH)/grub-editor.png
13 |
14 | uninstall:
15 | rm -f $(DESKTOP_PATH)/$(DESKTOP_FILE)
16 | rm -rf $(INSTALL_PATH)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # grub-editor
4 |
5 | GUI application to manage grub configuration
6 |
7 |
8 | It workes by editing the /etc/default/grub
9 |
10 |
11 | [Website](https://thenujan-0.github.io/grub-editor-web)
12 |
13 | [](https://opensource.org/licenses/MIT)
14 |
15 |
16 | 
17 |
18 | 
19 |
20 | # snapshots storage
21 |
22 | Snapshots of the configs are stored in ~/.local/share/grub-editor/snapshots/
--------------------------------------------------------------------------------
/__pycache__/find_entries.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Thenujan-0/grub-editor/2cfa9a263dcb5522f2cccb29a910de1b29858040/__pycache__/find_entries.cpython-310.pyc
--------------------------------------------------------------------------------
/docs/preferences.json.md:
--------------------------------------------------------------------------------
1 | #This file contains keys and all possible values of main.json file that stores the user preferences
2 |
3 | default way to view snapshots
4 | view_default:"on_the_application_itself","default_text_editor","None"
5 | take note that the None above is actually a string
6 |
7 | default way to create snapshots when loaded /etc/grub/default was modified in UI
8 | create_snapshot:"add_changes_to_snapshot","None","ignore_changes"
9 |
10 | show_invalid_default_entry:"True","False","None"
11 | invalid_kernel_version:"fix","cancel",:None
--------------------------------------------------------------------------------
/grub-editor.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Categories=Utility;System;
3 | Comment[en_US]=GUI application to edit grub configurations
4 | Comment=GUI application to edit grub configurations
5 | Exec=/opt/grub-editor/grub-editor.py
6 | GenericName[en_US]=
7 | GenericName=
8 | Icon=grub-editor
9 | MimeType=
10 | Name[en_US]=Grub Editor
11 | Name=Grub Editor
12 | Path=
13 | StartupNotify=true
14 | StartupWMClass=Grub Editor
15 | Terminal=false
16 | TerminalOptions=
17 | Type=Application
18 | X-DBUS-ServiceName=
19 | X-DBUS-StartupType=
20 | X-KDE-SubstituteUID=false
21 | X-KDE-Username=
22 |
--------------------------------------------------------------------------------
/grub-editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Thenujan-0/grub-editor/2cfa9a263dcb5522f2cccb29a910de1b29858040/grub-editor.png
--------------------------------------------------------------------------------
/grub-editor.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import subprocess
3 | import sys
4 | import os
5 | import traceback
6 | import logging
7 | from math import floor
8 |
9 | from grubEditor.main import main
10 |
11 |
12 | PATH=os.path.dirname(os.path.realpath(__file__))
13 |
14 | HOME =os.getenv('HOME')
15 | if os.getenv("XDG_DATA_HOME") is None:
16 | DATA_LOC=HOME+"/.local/share/grub-editor"
17 | else:
18 | DATA_LOC=os.getenv("XDG_DATA_HOME")+"/grub-editor"
19 |
20 | LOG_PATH=f'{DATA_LOC}/logs/main.log'
21 |
22 | logging.root.handlers = []
23 |
24 | logging.basicConfig(
25 | level=logging.ERROR,
26 | format="%(asctime)s [%(levelname)s] %(message)s",
27 | handlers=[
28 | logging.FileHandler(LOG_PATH),
29 | logging.StreamHandler()
30 | ]
31 | )
32 |
33 |
34 | size= os.path.getsize(LOG_PATH)
35 |
36 | if size>5*10**6:
37 | with open(LOG_PATH) as f:
38 | data = f.read()
39 | ind=floor(len(data)/2)
40 | new_data =data[ind:]
41 | with open(LOG_PATH,"w") as f:
42 | f.write(new_data)
43 |
44 | def except_hook(_,exception,__):
45 | # sys.__excepthook__(cls, exception, traceback)
46 | # logging.error(traceback.format_exc())
47 | error_text = "".join(traceback.format_exception(exception))
48 | logging.error("Unhandled exception: %s", error_text)
49 |
50 | #escape single quotes
51 | error_text = error_text.replace("'","''")
52 |
53 | print(error_text) #Incase error_dialog fails
54 | cmd=f"python3 {PATH}/grubEditor/widgets/error_dialog.py 'An Exception occured' '{error_text}'"
55 | subprocess.Popen([cmd],shell=True)
56 |
57 |
58 |
59 |
60 | sys.excepthook = except_hook
61 |
62 |
63 |
64 | if __name__ == '__main__':
65 | print('starting main')
66 | try:
67 | print(PATH)
68 | main()
69 | except Exception as e:
70 | print(traceback.format_exc()) #Incase error_dialog fails
71 | error_text = traceback.format_exc()
72 | error_text=error_text.replace("'","''")
73 | cmd=f"python3 {PATH}/grubEditor/widgets/error_dialog.py 'An Exception occured' '{error_text}'"
74 | subprocess.Popen(cmd,shell=True)
75 | exit(1)
--------------------------------------------------------------------------------
/grubEditor/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Thenujan-0/grub-editor/2cfa9a263dcb5522f2cccb29a910de1b29858040/grubEditor/__init__.py
--------------------------------------------------------------------------------
/grubEditor/__main__.py:
--------------------------------------------------------------------------------
1 | from .main import main
2 |
3 | if __name__ == "__main__":
4 | main()
5 |
--------------------------------------------------------------------------------
/grubEditor/core.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | import os
3 | import sys
4 | import math
5 | from datetime import datetime as dt
6 | from grubEditor.locations import CACHE_LOC, DATA_LOC
7 |
8 |
9 |
10 | class GRUB_CONF(str, Enum):
11 | GRUB_TIMEOUT = "GRUB_TIMEOUT="
12 | GRUB_DISABLE_OS_PROBER = "GRUB_DISABLE_OS_PROBER="
13 | GRUB_DEFAULT = "GRUB_DEFAULT="
14 | GRUB_TIMEOUT_STYLE = "GRUB_TIMEOUT_STYLE="
15 | GRUB_RECORDFAIL_TIMEOUT = "GRUB_RECORDFAIL_TIMEOUT="
16 | GRUB_CMDLINE_LINUX = "GRUB_CMDLINE_LINUX="
17 |
18 | def remove_quotes(value:str)->str:
19 | """ Removes double quotes or single quotes from the begining and the end
20 | Only if the exist in both places
21 | """
22 | if value[0]=='"' and value[-1]=='"':
23 | value=value[1:-1]
24 | elif value[0]=="'" and value[-1]=="'":
25 | value=value[1:-1]
26 |
27 | return value
28 |
29 | def printer(*args):
30 | """ writes to log and writes to console """
31 | time_now = dt.now()
32 | printer_temp=''
33 | for arg in args:
34 | printer_temp= printer_temp +' '+str(arg)
35 |
36 | if sys.platform == 'linux':
37 | if os.stat(f'{DATA_LOC}/logs/main.log').st_size > 5000000:#number is in bytes
38 |
39 | #only keep last half of the file
40 | with open(f'{DATA_LOC}/logs/main.log','r') as f:
41 | data =f.read()
42 | lendata = len(data)/2
43 | lendata=math.floor(lendata)
44 | new_data = data[lendata:]+'\n'
45 |
46 | with open(f'{DATA_LOC}/logs/main.log','w') as f:
47 | f.write(str(time_now)+new_data+'\n')
48 |
49 |
50 | with open(f'{DATA_LOC}/logs/main.log','a') as f:
51 | f.write(str(time_now)+printer_temp+'\n')
52 | print(printer_temp)
53 |
54 | class CONF_HANDLER():
55 | current_file :str = "/etc/default/grub"
56 |
57 |
58 |
59 | def get(self, name:GRUB_CONF,issues,read_file=None, remove_quotes_=False):
60 | """arguments are the string to look for
61 | and the list to append issues to
62 | Note: It does some minor edits to the value read from the file before
63 | returning it if name==GRUB_DEFAULT
64 | 1.if the value is not saved then check for double quotes, if it has
65 | double quotes then it will be removed if not then (Missing ") will be added
66 | 2.replace " >" with ">" to make sure it is found as invalid
67 |
68 | """
69 |
70 | if read_file is None:
71 | read_file=self.current_file
72 |
73 | #check if last character is = to avoid possible bugs
74 | if name[-1] != '=':
75 | raise ValueError("name passed for get_value doesnt contain = as last character")
76 |
77 | with open(read_file) as file:
78 | data =file.read()
79 | lines=data.splitlines()
80 |
81 | # found the name that is being looked for in a commented line
82 | found_commented=False
83 |
84 | val= None
85 | for line in lines:
86 | sline=line.strip()
87 | if sline.find(f"#{name}")==0:
88 | found_commented=True
89 |
90 | if sline.find("#")==0:
91 | continue
92 | elif sline.find(name)==0:
93 | start_index= line.find(name)+len(name)
94 |
95 | val=sline[start_index:]
96 |
97 | #remove the double quotes in the end and the begining
98 | if name==GRUB_CONF.GRUB_DEFAULT and val is not None:
99 | if val.find(">")>0 and val.find(" >")==-1:
100 | val =val.replace(">"," >")
101 | elif val.find(" >")>0:
102 | #GRUB default is obviously invalid to make sure that other functions detect that its invalid lets just
103 | val.replace(" >",">")
104 |
105 | if val !="saved" :
106 | if (val[0]=="\"" and val[-1]=='"') or (val[0]=="'" and val[0] == "'"):
107 | val=val[1:-1]
108 | elif not val.replace(" >","").isdigit():
109 | val+=" (Missing \")"
110 |
111 | if val is None:
112 | comment_issue_string =f"{name} is commented out in {read_file}"
113 |
114 | if found_commented and comment_issue_string not in issues:
115 | issues.append(comment_issue_string)
116 | else:
117 | issues.append(f"{name} was not found in {read_file}")
118 | elif remove_quotes_:
119 | val = remove_quotes(val)
120 | if name=="GRUB_DISABLE_OS_PROBER=" and val is None:
121 | val="true"
122 | return val
123 |
124 | def remove(self, name:GRUB_CONF,target_file=f"{CACHE_LOC}/temp.txt"):
125 | """ Removes the value from the value """
126 | if name[-1] != '=':
127 | raise ValueError("name passed for set_value doesn't contain = as last character")
128 |
129 | with open(target_file) as f:
130 | data=f.read()
131 |
132 | lines=data.splitlines()
133 |
134 | for ind ,line in enumerate(lines):
135 | sline=line.strip()
136 |
137 | #no need to read the line if it starts with #
138 | if sline.find("#")==0:
139 | continue
140 |
141 | elif sline.find(name)==0:
142 | lines[ind]=""
143 |
144 | to_write_data=""
145 |
146 | for line in lines:
147 | to_write_data+=line+"\n"
148 |
149 | with open(target_file,'w') as file:
150 | file.write(to_write_data)
151 |
152 | def set(self, name,val,target_file=f'{CACHE_LOC}/temp.txt'):
153 | """ writes the changes to target_file(default:~/.cache/grub-editor/temp.txt). call initialize_temp_file before start writing to temp.txt
154 | call self.saveConfs or cp the file from cache to original to finalize the changes
155 |
156 | Note: It does some minor edits to the value passed if name==GRUB_DEFAULT
157 | 1.add double quotes if necessary
158 | 2.replace " >" with ">"
159 | """
160 |
161 | if name[-1] != '=':
162 | raise ValueError("name passed for set_value doesn't contain = as last character")
163 |
164 | # if name not in available_conf_keys:
165 | # raise ValueError("name not in available_conf_keys :"+name)
166 |
167 | if name ==GRUB_CONF.GRUB_DEFAULT and val!="saved":
168 | if val[0]!='"' and val[-1]!='"':
169 | val='"'+val+'"'
170 | if " >" in val:
171 | val= val.replace(" >",">")
172 |
173 |
174 | with open(target_file) as f:
175 | data=f.read()
176 |
177 | lines=data.splitlines()
178 | old_val=None
179 | for ind ,line in enumerate(lines):
180 | sline=line.strip()
181 |
182 | #no need to read the line if it starts with #
183 | if sline.find("#")==0:
184 | continue
185 |
186 | elif sline.find(name)==0:
187 | start_index= line.find(name)+len(name)
188 | old_val=line[start_index:]
189 | if old_val!="":
190 | new_line =line.replace(old_val,val)
191 | lines[ind]=new_line
192 | else:
193 | print("empty string")
194 | lines[ind]=name+val
195 |
196 | to_write_data=""
197 |
198 | for line in lines:
199 | to_write_data+=line+"\n"
200 |
201 | #if line wasn't found
202 | if old_val is None:
203 | to_write_data+=name+val
204 |
205 | with open(target_file,'w') as file:
206 | file.write(to_write_data)
207 |
208 | conf_handler = CONF_HANDLER()
--------------------------------------------------------------------------------
/grubEditor/libs/__init__.py:
--------------------------------------------------------------------------------
1 | import os, sys;
2 | sys.path.append(os.path.dirname(os.path.realpath(__file__)))
--------------------------------------------------------------------------------
/grubEditor/libs/find_entries.py:
--------------------------------------------------------------------------------
1 | """This module is used to find the entries in the grub config file"""
2 |
3 | import traceback
4 | import subprocess
5 | import os
6 | GRUB_CONF_NONEDITABLE="/boot/grub/grub.cfg"
7 |
8 | class GrubConfigNotFound(Exception):
9 | """ Raised when /boot/grub/grub.cfg is not found """
10 | def __init__(self):
11 | super(Exception,self).__init__(f"Grub config file was not found at {GRUB_CONF_NONEDITABLE}")
12 |
13 |
14 | class MainEntry():
15 | parent=None
16 | title:str
17 | sub_entries=[]
18 | def __init__(self,title,sub_entries_):
19 | self.title = title
20 | self.sub_entries=sub_entries_
21 |
22 | def __repr__(self) -> str:
23 | to_return=""
24 | if len(self.sub_entries)==0:
25 | return "\nMainEntry(title:'"+self.title+"')"
26 |
27 | for sub_entry in self.sub_entries:
28 | to_return+="\n"+sub_entry.title
29 |
30 |
31 | to_return="\nMainEntry(title:'"+self.title+"', sub_entries :["+to_return+"])"
32 |
33 | return to_return
34 |
35 | def set_parents_for_children(self):
36 | for child in self.sub_entries:
37 | child.parent=self
38 |
39 |
40 | def echo(self):
41 | print('----------------------------------------------------------------')
42 | print('')
43 | print(self.title)
44 |
45 | print('printing sub_entries _____________')
46 | for sub_entry in self.sub_entries:
47 | print(sub_entry.title,'title of sub entry')
48 | print(sub_entry.parent,'parent of sub entry')
49 | print('finished printing for one big main entry')
50 | print('----------------')
51 | print('----------------')
52 | print('')
53 |
54 | def find_entries():
55 |
56 | cmd_find_entries=["awk -F\\' '$1==\"menuentry \" || $1==\"submenu \" "+
57 | "{print i++ \" : \" $2}; /\\tmenuentry / {print \"\\t\" i-1\">\"j++ \" : "+
58 | "\" $2};' "+GRUB_CONF_NONEDITABLE]
59 |
60 | out =subprocess.getoutput(cmd_find_entries)
61 | NO_SUCH_FILE_ERR_MSG=f"awk: fatal: cannot open file `{GRUB_CONF_NONEDITABLE}' for reading: No such file or directory"
62 | PERMISSION_ERR_MSG=f"awk: fatal: cannot open file `{GRUB_CONF_NONEDITABLE}' for reading: Permission denied"
63 |
64 | if NO_SUCH_FILE_ERR_MSG in out:
65 | raise GrubConfigNotFound
66 | elif PERMISSION_ERR_MSG in out:
67 | raise PermissionError(f"Permission denied to read {GRUB_CONF_NONEDITABLE}")
68 |
69 |
70 | main_entries=[]
71 | lines =out.splitlines()
72 | for line in lines:
73 |
74 | if line[0].isdigit():
75 | to_append=MainEntry(line[4:],[])
76 | main_entries.append(to_append)
77 |
78 | else:
79 | try:
80 | to_append=MainEntry(line[7:],[])
81 | main_entries[-1].sub_entries.append(to_append)
82 | except IndexError as e:
83 | print(traceback.format_exc())
84 | print(e)
85 | print('error occured as an entry that was thought to be a sub entry couldnt be added to last main entry on the list .\
86 | Error might have occured because the main_entries list is empty')
87 | print('--------------------------Printing the output of the command to find entries--------------------------------------')
88 | print(out)
89 | print("printing main entries",main_entries)
90 | print("printing to_append",to_append)
91 | print("line being parsed",line)
92 |
93 |
94 |
95 | for entry in main_entries:
96 | entry.set_parents_for_children()
97 |
98 | return main_entries
99 |
100 |
101 | if __name__=="__main__":
102 | print(find_entries())
103 |
--------------------------------------------------------------------------------
/grubEditor/libs/qt_functools.py:
--------------------------------------------------------------------------------
1 |
2 | ''' Some functions that are usefull in qt gui applications '''
3 | from PyQt5 import QtWidgets
4 |
5 | def reconnect(signal ,new_handler=None,old_handler=None):
6 | try:
7 | if old_handler is not None:
8 | while True:
9 | signal.disconnect(old_handler)
10 | else:
11 | signal.disconnect()
12 | except TypeError:
13 | pass
14 | if new_handler is not None:
15 | # printer(signal)
16 | signal.connect(new_handler)
17 |
18 | def insert_into(layout,index,widget):
19 | items=[]
20 | for i in reversed(range(index,layout.count())):
21 | item = layout.takeAt(i)
22 | if isinstance(item,QtWidgets.QWidgetItem):
23 | widget_ =item.widget()
24 | widget_.setParent(None)
25 | items.append(item)
26 | elif isinstance(item,QtWidgets.QSpacerItem):
27 | if item.sizePolicy().horizontalPolicy()==7:
28 | horizontalSpacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Expanding,\
29 | QtWidgets.QSizePolicy.Minimum)
30 | items.append(horizontalSpacer)
31 | else:
32 | raise Exception("Error in insert_into")
33 | else:
34 | raise Exception("Non handled case in insert_into")
35 | layout.addWidget(widget)
36 | for i in reversed(range(len(items))):
37 | item = items[i]
38 | if isinstance(item,QtWidgets.QWidgetItem):
39 | layout.addWidget(item.widget())
40 | elif isinstance(item,QtWidgets.QSpacerItem):
41 | layout.addItem(item)
42 | else:
43 | raise Exception("Non handled case in insert_into")
--------------------------------------------------------------------------------
/grubEditor/libs/worker.py:
--------------------------------------------------------------------------------
1 | from PyQt5 import QtCore,QtWidgets
2 |
3 | import traceback
4 | import sys
5 |
6 |
7 | class WorkerSignals(QtCore.QObject):
8 | '''
9 | Defines the signals available from a running worker thread.
10 |
11 | Supported signals are:
12 |
13 | finished
14 | No data
15 |
16 | error
17 | tuple (exctype, value, traceback.format_exc() )
18 |
19 | result
20 | object data returned from processing, anything
21 |
22 | started
23 | used when there is a first step to pass in the process has to be set to emit manually in the runner function
24 |
25 | output
26 | emits everyline in stdout
27 |
28 |
29 |
30 | '''
31 | finished = QtCore.pyqtSignal()
32 | error = QtCore.pyqtSignal(tuple)
33 | result = QtCore.pyqtSignal(object)
34 | output =QtCore.pyqtSignal(str)
35 | exception=QtCore.pyqtSignal(object)
36 |
37 | started=QtCore.pyqtSignal()
38 |
39 |
40 |
41 |
42 | class Worker(QtCore.QRunnable):
43 | '''
44 | Worker thread
45 |
46 | Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
47 |
48 | :param callback: The function callback to run on this worker thread. Supplied args and
49 | kwargs will be passed through to the runner.
50 | :type callback: function
51 | :param args: Arguments to pass to the callback function
52 | :param kwargs: Keywords to pass to the callback function
53 |
54 | '''
55 |
56 | def __init__(self, fn, *args, **kwargs):
57 | super(Worker, self).__init__()
58 | # Store constructor arguments (re-used for processing)
59 | self.fn = fn
60 | self.args = args
61 | self.kwargs = kwargs
62 | self.signals = WorkerSignals()
63 |
64 |
65 | # this is a decorator
66 | # now when this run function is executed it actually calls the QtCore.pyqtSlot function wuth run function as argument
67 | @QtCore.pyqtSlot()
68 | def run(self):
69 | '''
70 | Initialise the runner function with passed args, kwargs.
71 | '''
72 |
73 | # Retrieve args/kwargs here; and fire processing using them
74 | try:
75 | result = self.fn(
76 | *self.args, **self.kwargs
77 | )
78 | except Exception as e:
79 | traceback.print_exc()
80 | exctype, value = sys.exc_info()[:2]
81 | self.signals.error.emit((exctype, value, traceback.format_exc()))
82 | self.signals.exception.emit(e)
83 | else:
84 | self.signals.result.emit(result) # Return the result of the processing
85 | finally:
86 | self.signals.finished.emit() # Done
--------------------------------------------------------------------------------
/grubEditor/locations.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | GRUB_CONF_LOC='/etc/default/grub'
4 | file_loc=GRUB_CONF_LOC
5 | HOME =os.getenv('HOME')
6 |
7 | if os.getenv("XDG_CONFIG_HOME") is None:
8 | CONFIG_LOC=HOME+"/.config/grub-editor"
9 | else:
10 | CONFIG_LOC=os.getenv("XDG_CONFIG_HOME")+"/grub-editor"
11 |
12 | if os.getenv("XDG_CACHE_HOME") is None:
13 | CACHE_LOC=HOME+"/.cache/grub-editor"
14 | else:
15 | CACHE_LOC=os.getenv("XDG_CACHE_HOME")+"/grub-editor"
16 |
17 | if os.getenv("XDG_DATA_HOME") is None:
18 | DATA_LOC=HOME+"/.local/share/grub-editor"
19 | else:
20 | DATA_LOC=os.getenv("XDG_DATA_HOME")+"/grub-editor"
21 |
22 |
--------------------------------------------------------------------------------
/grubEditor/ui/chroot.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 939
10 | 554
11 |
12 |
13 |
14 | Form
15 |
16 |
17 | -
18 |
19 |
20 | 40
21 |
22 |
23 | 0
24 |
25 |
-
26 |
27 |
-
28 |
29 |
30 | Chroot is accessing another operating system from this operating system. Grub-editor uses manjaro-chroot to chroot
31 |
32 |
33 | true
34 |
35 |
36 |
37 |
38 |
39 | -
40 |
41 |
42 | Select an operating system to chroot into
43 |
44 |
45 |
-
46 |
47 |
-
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/grubEditor/ui/chroot_after.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 584
10 | 459
11 |
12 |
13 |
14 | Form
15 |
16 |
17 | -
18 |
19 |
20 | 30
21 |
22 |
23 | 50
24 |
25 |
-
26 |
27 |
-
28 |
29 |
30 | Chrooted successfully into kde neon
31 |
32 |
33 | Qt::AlignCenter
34 |
35 |
36 |
37 |
38 |
39 | -
40 |
41 |
42 | Qt::Vertical
43 |
44 |
45 |
46 | 20
47 | 40
48 |
49 |
50 |
51 |
52 | -
53 |
54 |
55 | 20
56 |
57 |
-
58 |
59 |
60 | reinstall grub package
61 |
62 |
63 |
64 |
65 |
66 |
67 | 64
68 | 64
69 |
70 |
71 |
72 | Qt::ToolButtonTextUnderIcon
73 |
74 |
75 |
76 | -
77 |
78 |
79 | reinstall grub to device
80 |
81 |
82 |
83 |
84 |
85 |
86 | 64
87 | 64
88 |
89 |
90 |
91 | Qt::ToolButtonTextUnderIcon
92 |
93 |
94 |
95 |
96 |
97 | -
98 |
99 |
100 | Qt::Vertical
101 |
102 |
103 |
104 | 20
105 | 40
106 |
107 |
108 |
109 |
110 | -
111 |
112 |
113 | 0
114 |
115 |
116 | 0
117 |
118 |
-
119 |
120 |
121 | Qt::Horizontal
122 |
123 |
124 |
125 | 40
126 | 20
127 |
128 |
129 |
130 |
131 | -
132 |
133 |
134 | update grub configuration
135 |
136 |
137 |
138 |
139 |
140 |
141 | 64
142 | 64
143 |
144 |
145 |
146 | Qt::ToolButtonTextUnderIcon
147 |
148 |
149 |
150 | -
151 |
152 |
153 | Qt::Horizontal
154 |
155 |
156 |
157 | 40
158 | 20
159 |
160 |
161 |
162 |
163 | -
164 |
165 |
166 |
167 | 180
168 | 0
169 |
170 |
171 |
172 | exit chroot
173 |
174 |
175 |
176 |
177 |
178 |
179 | 64
180 | 64
181 |
182 |
183 |
184 | Qt::ToolButtonTextUnderIcon
185 |
186 |
187 |
188 | -
189 |
190 |
191 | Qt::Horizontal
192 |
193 |
194 |
195 | 40
196 | 20
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
--------------------------------------------------------------------------------
/grubEditor/ui/chroot_loading.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 903
10 | 623
11 |
12 |
13 |
14 | Form
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 | 30
23 |
24 |
-
25 |
26 |
27 |
28 | 12
29 | 75
30 | true
31 |
32 |
33 |
34 | Please wait .Looking for other operating systems using os-prober. This might take a While
35 |
36 |
37 |
38 |
39 |
40 | -
41 |
42 |
43 | Output of os-prober
44 |
45 |
46 |
47 | -
48 |
49 |
-
50 |
51 |
52 | true
53 |
54 |
55 |
56 |
57 | 0
58 | 0
59 | 883
60 | 516
61 |
62 |
63 |
64 |
-
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/grubEditor/ui/create_snapshot_dialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 649
10 | 222
11 |
12 |
13 |
14 | Create snapshot
15 |
16 |
17 |
18 | -
19 |
20 |
21 | 10
22 |
23 |
-
24 |
25 |
26 | you have made changes to the loaded configurations. Do you want to take snap shot of the current /etc/default/grub file which would mean that the changes you have done would be ignored . Or do you want to add these changes to the snapshot
27 |
28 |
29 | true
30 |
31 |
32 |
33 | -
34 |
35 |
36 | Do this everytime
37 |
38 |
39 |
40 | -
41 |
42 |
-
43 |
44 |
45 | Ignore the changes
46 |
47 |
48 |
49 | -
50 |
51 |
52 | add changes to snapshot
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/grubEditor/ui/dialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 604
10 | 297
11 |
12 |
13 |
14 | Dialog
15 |
16 |
17 | -
18 |
19 |
20 | 20
21 |
22 |
-
23 |
24 |
25 | The partition you have selected is not empty and is too big for your system specs.It is recommended that you create another partition using gparted or kparted and then try again.
26 |
27 |
28 | true
29 |
30 |
31 |
32 | -
33 |
34 |
35 | Never show this to me again
36 |
37 |
38 |
39 | -
40 |
41 |
42 | 6
43 |
44 |
-
45 |
46 |
47 | Cancel
48 |
49 |
50 |
51 | -
52 |
53 |
54 | Qt::Horizontal
55 |
56 |
57 |
58 | 40
59 | 20
60 |
61 |
62 |
63 |
64 | -
65 |
66 |
67 | OK
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/grubEditor/ui/issues.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 655
10 | 443
11 |
12 |
13 |
14 | Issues
15 |
16 |
17 |
18 | -
19 |
20 |
-
21 |
22 |
23 |
24 | 15
25 |
26 |
27 |
28 | Some issues were found on the /etc/default/grub
29 |
30 |
31 |
32 | -
33 |
34 |
35 | -
36 |
37 |
38 | Grub-editor might not be able to function properly because of these issues.Please fix these issues manually before proceeding
39 |
40 |
41 | true
42 |
43 |
44 |
45 | -
46 |
47 |
48 | 10
49 |
50 |
-
51 |
52 |
53 | Quit
54 |
55 |
56 |
57 | -
58 |
59 |
60 | Open /etc/default/grub
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/grubEditor/ui/main.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1007
10 | 556
11 |
12 |
13 |
14 |
15 | 700
16 | 0
17 |
18 |
19 |
20 | Grub Editor
21 |
22 |
23 |
24 |
25 | 0
26 |
27 | -
28 |
29 |
30 |
31 | 1000
32 | 16777215
33 |
34 |
35 |
36 | 0
37 |
38 |
39 |
40 |
41 | 1000
42 | 16777215
43 |
44 |
45 |
46 | Edit configurations
47 |
48 |
49 |
-
50 |
51 |
52 | 0
53 |
54 |
-
55 |
56 |
57 | 30
58 |
59 |
-
60 |
61 |
62 | Loaded configuration from
63 |
64 |
65 |
66 | -
67 |
68 |
-
69 |
70 | /etc/default/grub
71 |
72 |
73 |
74 |
75 |
76 |
77 | -
78 |
79 |
80 | 0
81 |
82 |
83 | 0
84 |
85 |
-
86 |
87 |
88 | Look for other operating systems
89 |
90 |
91 |
92 | -
93 |
94 |
95 | Visiblity
96 |
97 |
98 |
99 | 20
100 |
101 |
-
102 |
103 |
104 | Show menu
105 |
106 |
107 |
108 | -
109 |
110 |
111 | 0
112 |
113 |
114 | 0
115 |
116 |
-
117 |
118 |
119 | Boot default entry after
120 |
121 |
122 |
123 | -
124 |
125 |
126 | -
127 |
128 |
129 |
130 | 40
131 | 0
132 |
133 |
134 |
135 |
136 | 40
137 | 40
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | ..
146 |
147 |
148 |
149 | 20
150 | 20
151 |
152 |
153 |
154 |
155 | -
156 |
157 |
158 |
159 | 0
160 | 0
161 |
162 |
163 |
164 |
165 | 40
166 | 40
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | ..
175 |
176 |
177 |
178 | -
179 |
180 |
181 | seconds
182 |
183 |
184 |
185 |
186 |
187 | -
188 |
189 |
190 | Force zero timeout
191 |
192 |
193 |
194 |
195 |
196 |
197 | -
198 |
199 |
200 |
201 | 1000
202 | 16777215
203 |
204 |
205 |
206 | Default entry
207 |
208 |
209 |
-
210 |
211 |
212 | 0
213 |
214 |
215 | 10
216 |
217 |
-
218 |
219 |
220 | predefined:
221 |
222 |
223 |
224 | -
225 |
226 |
227 |
228 | 1
229 | 0
230 |
231 |
232 |
233 |
234 |
235 |
236 | -
237 |
238 |
239 | previously booted entry
240 |
241 |
242 |
243 |
244 |
245 |
246 | -
247 |
248 |
249 | 15
250 |
251 |
-
252 |
253 |
254 | Reset
255 |
256 |
257 |
258 | -
259 |
260 |
261 | set
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 | conf snapshots
276 |
277 |
278 | -
279 |
280 |
281 | create a snapshot now
282 |
283 |
284 |
285 | -
286 |
287 |
288 | Looks like you dont have any snapshots .Snapshots are backups of /etc/default/grub .Snapshots can help you when you mess up some configuration in /etc/default/grub . These snapshots are stored inside ~/.grub-editor/snapshots/
289 |
290 |
291 | true
292 |
293 |
294 |
295 | -
296 |
297 |
298 | Qt::ScrollBarAlwaysOff
299 |
300 |
301 | QAbstractScrollArea::AdjustToContents
302 |
303 |
304 | true
305 |
306 |
307 |
308 |
309 | 0
310 | 0
311 | 971
312 | 347
313 |
314 |
315 |
316 |
-
317 |
318 |
319 | 40
320 |
321 |
-
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
346 |
347 |
348 |
349 |
350 |
351 |
--------------------------------------------------------------------------------
/grubEditor/ui/main1.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1007
10 | 595
11 |
12 |
13 |
14 |
15 | 700
16 | 0
17 |
18 |
19 |
20 | Grub Editor
21 |
22 |
23 |
24 |
25 | 0
26 |
27 | -
28 |
29 |
30 | 0
31 |
32 |
33 |
34 | Edit configurations
35 |
36 |
37 |
-
38 |
39 |
40 | 0
41 |
42 |
-
43 |
44 |
45 | 30
46 |
47 |
-
48 |
49 |
50 | Loaded configuration from
51 |
52 |
53 |
54 | -
55 |
56 |
-
57 |
58 | /etc/default/grub
59 |
60 |
61 |
62 |
63 |
64 |
65 | -
66 |
67 |
68 | 0
69 |
70 |
71 | 0
72 |
73 |
-
74 |
75 |
76 | 15
77 |
78 |
-
79 |
80 |
81 | Reset
82 |
83 |
84 |
85 | -
86 |
87 |
88 | set
89 |
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 | Default entry
98 |
99 |
100 |
-
101 |
102 |
103 | previously booted entry
104 |
105 |
106 |
107 | -
108 |
109 |
110 | 0
111 |
112 |
113 | 10
114 |
115 |
-
116 |
117 |
118 | predefined:
119 |
120 |
121 |
122 | -
123 |
124 |
125 |
126 | 1
127 | 0
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | -
138 |
139 |
140 | Look for other operating systems
141 |
142 |
143 |
144 | -
145 |
146 |
147 | Visiblity
148 |
149 |
150 |
-
151 |
152 |
153 | 6
154 |
155 |
156 | 0
157 |
158 |
-
159 |
160 |
161 | 0
162 |
163 |
164 | 0
165 |
166 |
-
167 |
168 |
169 | Boot default entry after
170 |
171 |
172 |
173 | -
174 |
175 |
176 | true
177 |
178 |
179 |
180 | 200
181 | 0
182 |
183 |
184 |
185 |
186 |
187 |
188 | QFrame::StyledPanel
189 |
190 |
191 | QFrame::Raised
192 |
193 |
194 |
195 | 0
196 |
197 |
198 | 0
199 |
200 |
-
201 |
202 |
-
203 |
204 |
205 | -
206 |
207 |
208 |
209 | 40
210 | 0
211 |
212 |
213 |
214 |
215 | 40
216 | 40
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 | ..
225 |
226 |
227 |
228 | 20
229 | 20
230 |
231 |
232 |
233 |
234 | -
235 |
236 |
237 |
238 | 0
239 | 0
240 |
241 |
242 |
243 |
244 | 40
245 | 40
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 | ..
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 | -
263 |
264 |
265 | seconds
266 |
267 |
268 |
269 |
270 |
271 | -
272 |
273 |
274 | 0
275 |
276 |
-
277 |
278 |
279 | Show menu
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 | conf snapshots
299 |
300 |
301 | -
302 |
303 |
304 | create a snapshot now
305 |
306 |
307 |
308 | -
309 |
310 |
311 | Looks like you dont have any snapshots .Snapshots are backups of /etc/default/grub .Snapshots can help you when you mess up some configuration in /etc/default/grub . These snapshots are stored inside ~/.grub-editor/snapshots/
312 |
313 |
314 | true
315 |
316 |
317 |
318 | -
319 |
320 |
321 | Qt::ScrollBarAlwaysOff
322 |
323 |
324 | QAbstractScrollArea::AdjustToContents
325 |
326 |
327 | true
328 |
329 |
330 |
331 |
332 | 0
333 | 0
334 | 971
335 | 386
336 |
337 |
338 |
339 |
-
340 |
341 |
342 | 40
343 |
344 |
-
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
369 |
370 |
371 |
372 |
373 |
374 |
--------------------------------------------------------------------------------
/grubEditor/ui/progress.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 584
10 | 217
11 |
12 |
13 |
14 | MainWindow
15 |
16 |
17 |
18 | -
19 |
20 |
21 | 30
22 |
23 |
-
24 |
25 |
-
26 |
27 |
28 | Chrooting please wait
29 |
30 |
31 | Qt::AlignCenter
32 |
33 |
34 |
35 |
36 |
37 | -
38 |
39 |
40 | true
41 |
42 |
43 |
44 |
45 | 0
46 | 0
47 | 545
48 | 110
49 |
50 |
51 |
52 |
-
53 |
54 |
-
55 |
56 |
57 | this is a big
58 | paragraph of text
59 | or should i say
60 | very vey
61 | very big paragraph of text
62 |
63 |
64 | true
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/grubEditor/ui/set_recommendations.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 449
10 | 272
11 |
12 |
13 |
14 | MainWindow
15 |
16 |
17 |
18 | -
19 |
20 |
21 | 0
22 |
23 |
-
24 |
25 |
26 | Ignore all
27 |
28 |
29 |
30 | -
31 |
32 |
33 | Fix all
34 |
35 |
36 |
37 |
38 |
39 | -
40 |
41 |
-
42 |
43 |
44 | true
45 |
46 |
47 |
48 |
49 | 0
50 | 0
51 | 427
52 | 123
53 |
54 |
55 |
56 |
-
57 |
58 |
59 | 0
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | -
70 |
71 |
72 |
73 | 12
74 |
75 |
76 |
77 | Recommendations regarding your configurations
78 |
79 |
80 |
81 |
82 |
83 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/grubEditor/ui/snapshots_.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 641
10 | 320
11 |
12 |
13 |
14 | Dialog
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 | Looks like you made some changes to the file that was loaded.
23 | Note that the changes you have done through the gui will not be added to the snap shot.
24 | Inorder to add those changes to the snapshot you can take a snapshot of your currently loaded configuration and then save the changes and then create a snapshot and then revert back
25 |
26 |
27 | true
28 |
29 |
30 |
31 |
32 |
33 | -
34 |
35 |
36 | Qt::Horizontal
37 |
38 |
39 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | buttonBox
49 | accepted()
50 | Dialog
51 | accept()
52 |
53 |
54 | 248
55 | 254
56 |
57 |
58 | 157
59 | 274
60 |
61 |
62 |
63 |
64 | buttonBox
65 | rejected()
66 | Dialog
67 | reject()
68 |
69 |
70 | 316
71 | 260
72 |
73 |
74 | 286
75 | 274
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/grubEditor/ui/treeWidgetTest.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 800
10 | 600
11 |
12 |
13 |
14 | MainWindow
15 |
16 |
17 |
18 | -
19 |
20 |
21 |
22 | Operating system
23 |
24 |
25 |
-
26 |
27 | manjaro
28 |
29 |
30 | -
31 |
32 | advanced options for manjaro
33 |
34 |
-
35 |
36 | manjaro 5.14
37 |
38 |
39 | -
40 |
41 | manjaro 5.15
42 |
43 |
44 | -
45 |
46 | manjaro 5.15 fallback
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/grubEditor/widgets/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Thenujan-0/grub-editor/2cfa9a263dcb5522f2cccb29a910de1b29858040/grubEditor/widgets/__init__.py
--------------------------------------------------------------------------------
/grubEditor/widgets/dialog.py:
--------------------------------------------------------------------------------
1 | from PyQt5 import QtWidgets ,uic
2 | from PyQt5.QtWidgets import QDesktopWidget,QApplication
3 | import os
4 | import sys
5 |
6 |
7 | #remove /widgets part from path
8 | PATH = os.path.dirname(os.path.realpath(__file__))
9 | PATH = PATH[0:-8]
10 |
11 | sys.path.append(PATH)
12 |
13 |
14 | class DialogUi(QtWidgets.QDialog):
15 | """ Create a dialog
16 | Available functions
17 | 1.setText
18 | 2.setBtnOkText
19 | 3.exitOnAny (if you want to exit the app on closing the dialog or clicking ok or cancel)
20 | 4.exitOnClose
21 | 5.exitOnCancel
22 |
23 | """
24 | _exitOnclose=False
25 |
26 | def __init__(self,btn_cancel=True):
27 | super(DialogUi,self).__init__()
28 | uic.loadUi(f'{PATH}/ui/dialog.ui',self)
29 | if not btn_cancel:
30 | self.horizontalLayout.takeAt(0).widget().deleteLater()
31 | else:
32 | self.btn_cancel.clicked.connect(self._btn_cancel_callback)
33 | self.btn_ok.clicked.connect(self._btn_ok_callback)
34 |
35 |
36 | #make sure window is in center of the screen
37 | qtRectangle = self.frameGeometry()
38 | centerPoint = QDesktopWidget().availableGeometry().center()
39 | qtRectangle.moveCenter(centerPoint)
40 | self.move(qtRectangle.topLeft())
41 |
42 | def _btn_ok_callback(self):
43 | self.close()
44 |
45 | def _btn_cancel_callback(self):
46 | self.close()
47 |
48 | def show_dialog(self):
49 | """ Decides on showing the dialog using the preferences file and the value of label that is set """
50 |
51 | self.show()
52 |
53 | def remove_btn_cancel(self):
54 | self.horizontalLayout.takeAt(0).widget().deleteLater()
55 | self.btn_cancel.setParent(None)
56 |
57 | def add_btn_cancel(self):
58 | self.btn_cancel=QtWidgets.QPushButton()
59 | self.btn_cancel.setText("Cancel")
60 | self.horizontalLayout.insertWidget(0,self.btn_cancel)
61 | self.btn_cancel.clicked.connect(self._btn_cancel_callback)
62 |
63 | def setText(self,text):
64 | self.label.setText(text)
65 |
66 | def setBtnOkText(self,text):
67 | self.btn_ok.setText(text)
68 |
69 | def removeCheckBox(self):
70 | self.checkBox.setParent(None)
71 | self.checkBox.deleteLater()
72 |
73 | def _exitApp(self):
74 | QApplication.quit()
75 |
76 | def exitOnAny(self):
77 | self.btn_ok.clicked.connect(self._exitApp)
78 | self.exitOnclose=True
79 | self.btn_cancel.clicked.connect(self._exitApp)
80 |
81 | def exitOnCancel(self):
82 | self.btn_cancel.clicked.connect(self._exitApp)
83 |
84 | def exitOnClose(self):
85 | self._exitOnClose=True
86 |
87 | def closeEvent(self,event):
88 | if self._exitOnclose:
89 | self._exitApp()
90 | else:
91 | event.accept()
92 |
93 | def main():
94 | global PATH
95 |
96 | print(PATH)
97 | app = QtWidgets.QApplication([])
98 | window = DialogUi()
99 | window.show()
100 | app.exec_()
101 |
102 | if __name__ == '__main__':
103 | main()
--------------------------------------------------------------------------------
/grubEditor/widgets/elided_label.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtCore import Qt
2 | from PyQt5.QtGui import QPainter,QFontMetrics
3 | from PyQt5.QtWidgets import QLabel
4 |
5 | class ElidedLabel(QLabel):
6 | def paintEvent(self, event):
7 | painter = QPainter(self)
8 | metrics = QFontMetrics(self.font())
9 | elided = metrics.elidedText(self.text(), Qt.ElideRight, self.width())
10 | painter.drawText(self.rect(), self.alignment(), elided)
--------------------------------------------------------------------------------
/grubEditor/widgets/error_dialog.py:
--------------------------------------------------------------------------------
1 | from PyQt5 import QtWidgets ,uic,QtGui
2 | from PyQt5.QtWidgets import QDesktopWidget,QApplication
3 | import os
4 | import sys
5 |
6 | PATH = os.path.dirname(os.path.realpath(__file__))
7 |
8 |
9 |
10 | class ErrorDialogUi(QtWidgets.QDialog):
11 | """ Avaiable functions are
12 | set_error_title
13 | set_error_body
14 |
15 | """
16 |
17 | exitOnclose=False
18 |
19 | def __init__(self,):
20 | super(ErrorDialogUi,self).__init__()
21 | print(PATH)
22 | uic.loadUi(f'{PATH}/ui/error_dialog.ui',self)
23 |
24 | self.btn_ok.clicked.connect(self.selfClose)
25 | self.btn_copy.clicked.connect(self.onCopy)
26 |
27 |
28 | #make sure window is in center of the screen
29 | qtRectangle = self.frameGeometry()
30 | centerPoint = QDesktopWidget().availableGeometry().center()
31 | qtRectangle.moveCenter(centerPoint)
32 | self.move(qtRectangle.topLeft())
33 |
34 |
35 | def selfClose(self):
36 | self.close()
37 |
38 | def set_error_title(self,error_title):
39 | ''' Sets the title for error message dont use dot in the end as dot will be placed automatically
40 | '''
41 |
42 | #
An error has occured.Please consider reporting this to github page
43 | #is the main error message
44 |
45 | #only part thats going to be replaced is An error has occured
46 |
47 | text = self.lbl_error_title.text()
48 | self.lbl_error_title.setText(text.replace('An error has occured',error_title))
49 |
50 | def set_error_body(self,error_body):
51 | self.lbl_error_body.setText(error_body)
52 |
53 | def _exitApp(self):
54 | QApplication.exit()
55 | def exitOnAny(self):
56 | self.btn_ok.clicked.connect(self._exitApp)
57 | self.exitOnclose=True
58 |
59 |
60 | def closeEvent(self,event):
61 | if self.exitOnclose:
62 | self._exitApp()
63 | else:
64 | event.accept()
65 |
66 | def onCopy(self):
67 | QApplication.clipboard().setText(self.lbl_error_body.text())
68 |
69 | def main():
70 | app= QtWidgets.QApplication([])
71 | window=ErrorDialogUi()
72 |
73 | try:
74 | window.set_error_title(sys.argv[1])
75 | window.set_error_body(sys.argv[2])
76 |
77 | except IndexError:
78 | pass
79 | window.show()
80 | app.setWindowIcon(QtGui.QIcon('/usr/share/pixmaps/grub-editor.png'))
81 |
82 | sys.exit(app.exec_())
83 |
84 |
85 | if __name__ =="__main__":
86 | main()
--------------------------------------------------------------------------------
/grubEditor/widgets/loading_bar.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 |
4 | import sys
5 | from PyQt5.QtWidgets import QWidget, QApplication
6 | from PyQt5.QtGui import QPainter, QColor, QFont,QPalette,QPolygonF,QBrush
7 | from PyQt5.QtCore import Qt , QPointF,QRectF,QObject,pyqtSignal,QRunnable,pyqtSlot,QThreadPool,QTimer
8 | import traceback
9 | from time import sleep
10 | from functools import partial
11 |
12 |
13 | class WorkerSignals(QObject):
14 | """ defines the signals available from a running worker thread
15 |
16 | supported signals are:
17 | finished
18 | No data
19 |
20 | error
21 | tuple( exctype ,value ,traceback.format_exc() )
22 |
23 | result
24 | object data returned from processing , anything
25 |
26 |
27 | """
28 |
29 | finished = pyqtSignal()
30 | error= pyqtSignal(tuple)
31 | result = pyqtSignal(object)
32 |
33 | class Worker(QRunnable):
34 | """
35 | Worker thread
36 | inherits from QRunnable to handler worker thread setup , signals, wrap-up.
37 | :param callback: The function to run on this worker thread . Supllied args and
38 | kwargs will be passed through the runner
39 | :type callback: function
40 | :param args : Arguments to pass the callback function
41 | :param kwargs : keyword to pass to the callback function
42 |
43 | """
44 |
45 | def __init__(self,fn,*args,**kwargs):
46 | super(Worker, self).__init__()
47 | self.fn =fn
48 | self.args= args
49 | self.kwargs=kwargs
50 | self.signals=WorkerSignals()
51 |
52 |
53 | @pyqtSlot()
54 | def run(self):
55 | """
56 | initialise the runner function with passed args and kwargs
57 | """
58 |
59 | try:
60 | result =self.fn(*self.args,**self.kwargs)
61 |
62 | except:
63 | traceback.print_exc()
64 | exctype,value = sys.exc_info()[:2]
65 | self.signals.error.emit((exctype, value, traceback.format_exc() ))
66 | else:
67 | self.signals.result.emit(result)
68 | finally:
69 | self.signals.finished.emit()
70 |
71 |
72 |
73 | class LoadingBar(QWidget):
74 |
75 | def __init__(self):
76 | super().__init__()
77 | self.threadpool=QThreadPool()
78 |
79 | self.initUI()
80 |
81 | #position of colored part in loading bar 0 -100
82 | self.position=20
83 | self.startWorker(self.move_loading_bar)
84 | self.loading_increasing=True
85 | self.interruptRequested = False
86 |
87 |
88 | def onDestroyed(self):
89 | self.interruptRequested = True
90 |
91 | #For some reason it doesn't work if i directly make onDestroyed a method of this class
92 | self.destroyed.connect(partial(onDestroyed,self))
93 |
94 | def move_loading_bar(self):
95 | """ move the loading bar back and forth by changing the value of self.position """
96 | while not self.interruptRequested:
97 | # print('moving loading bar',self.position)
98 | sleep(0.015)
99 | if self.position ==100:
100 | self.loading_increasing=False
101 | elif self.position==0:
102 | self.loading_increasing=True
103 |
104 | if self.loading_increasing:
105 | self.position+=1
106 | else:
107 | self.position-=1
108 |
109 |
110 | #Error might occur if the LoadingBar widget is deleted so to catch that
111 | try:
112 | self.update()
113 | except RuntimeError:
114 | pass
115 |
116 |
117 | def startWorker(self,fn,*args,**kwargs):
118 | worker= Worker(fn)
119 |
120 | self.threadpool.start(worker)
121 |
122 |
123 | def initUI(self):
124 |
125 |
126 | self.setGeometry(300, 300, 350, 300)
127 | self.setWindowTitle('loading please wait')
128 | self.show()
129 |
130 | def paintEvent(self, event):
131 | qp = QPainter()
132 | qp.begin(self)
133 | self.drawText(event, qp)
134 | qp.end()
135 |
136 | def drawText(self, event, qp):
137 |
138 | width = self.width()
139 | height = self.height()
140 |
141 | self.widget_height= 6
142 |
143 |
144 | #the part of the loading bar that is not going to have the progressed part
145 | reduce_amount = width*0.6
146 |
147 | top_left =QPointF(int(width*0.1),int(height/2-self.widget_height/2))
148 | bottom_right =QPointF(int(width*0.9)-reduce_amount ,int(height/2 +self.widget_height/2))
149 |
150 | bigger_bottom_right =QPointF(int(width*0.9) ,int(height/2+self.widget_height/2) )
151 |
152 | recty =QRectF(QPointF(top_left.x()+self.position/100*reduce_amount,top_left.y()),
153 | QPointF(bottom_right.x()+self.position/100*reduce_amount,bottom_right.y()))
154 | bigger_recty=QRectF(top_left,bigger_bottom_right)
155 |
156 |
157 | #non progressed part (bigger rounded rect)
158 | qp.setPen(QPalette().color(QPalette.Disabled,QPalette.Text))
159 | qp.setBrush(QBrush(QPalette().color(QPalette.Active,QPalette.Button)))
160 | qp.drawRoundedRect(bigger_recty,3,3)
161 |
162 |
163 | #progressed part
164 | qp.setBrush(QBrush(QPalette().color(QPalette().Inactive,QPalette().Highlight)))
165 | qp.setPen(QPalette().color(QPalette().Active,QPalette().Highlight))
166 | qp.drawRoundedRect(recty,2,2)
167 |
168 |
169 | def main():
170 |
171 | app = QApplication(sys.argv)
172 | ex = LoadingBar()
173 | sys.exit(app.exec_())
174 |
175 |
176 | if __name__ == '__main__':
177 | main()
--------------------------------------------------------------------------------
/grubEditor/widgets/progress.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 |
4 | from PyQt5 import QtWidgets,QtCore
5 |
6 |
7 | PATH= os.path.dirname(os.path.realpath(__file__))
8 |
9 |
10 | if 'widgets' == PATH[-7:]:
11 | print(PATH[0:-8])
12 | sys.path.append(PATH[0:-8])
13 |
14 | import widgets.loading_bar as loading_bar
15 | from grubEditor.libs.qt_functools import insert_into
16 |
17 |
18 | class ProgressUi(QtWidgets.QMainWindow):
19 | def __init__(self):
20 | super().__init__()
21 |
22 |
23 | self.centralwidget=QtWidgets.QWidget(self)
24 | self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
25 | self.verticalLayout=QtWidgets.QVBoxLayout()
26 | self.verticalLayout.setContentsMargins(-1, -1, -1, 30)
27 | self.lbl_status=QtWidgets.QLabel(self.centralwidget)
28 | self.lbl_status.setAlignment(QtCore.Qt.AlignCenter)
29 | self.lbl_status.setText("please wait")
30 | self.verticalLayout.addWidget(self.lbl_status)
31 | self.gridLayout.addLayout(self.verticalLayout,0,0,1,1)
32 | self.setCentralWidget(self.centralwidget)
33 | self.loading_bar=loading_bar.LoadingBar()
34 | self.loading_bar.setMinimumHeight(20)
35 | self.verticalLayout.addWidget(self.loading_bar)
36 |
37 | self.btn_show_details=QtWidgets.QPushButton(self.centralwidget)
38 | self.btn_show_details.setText("Show details")
39 | self.verticalLayout.addWidget(self.btn_show_details)
40 |
41 |
42 | self.resize(400,200)
43 |
44 |
45 | self.lbl_details_text=''
46 | self.lbl_details=None
47 |
48 | self.btn_show_details.clicked.connect(self.btn_show_details_callback)
49 |
50 |
51 |
52 |
53 | def btn_show_details_callback(self):
54 | btn=self.sender()
55 | text =btn.text()
56 | if self.verticalLayout.itemAt(3) is not None:
57 | print(self.verticalLayout.itemAt(3).widget())
58 | else:
59 | print(None)
60 | if text =='Show details':
61 | self.scrollArea=QtWidgets.QScrollArea(self.centralwidget)
62 | self.scrollArea.setWidgetResizable(True)
63 | self.scrollAreaWidgetContents=QtWidgets.QWidget()
64 | self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 545, 110))
65 | self.gridLayout_2 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents)
66 | self.verticalLayout_2 = QtWidgets.QVBoxLayout()
67 | self.lbl_details = QtWidgets.QLabel(self.scrollAreaWidgetContents)
68 | self.lbl_details.setWordWrap(True)
69 |
70 | self.lbl_details.setText(self.lbl_details_text)
71 |
72 | #find the index of the button
73 | for index in range(self.verticalLayout.count()):
74 | item = self.verticalLayout.itemAt(index).widget()
75 | if 'QPushButton' in item.__str__() and item.text()=='Show details':
76 |
77 | self.verticalLayout_2.addWidget(self.lbl_details)
78 | self.gridLayout_2.addLayout(self.verticalLayout_2, 0, 0, 1, 1)
79 | self.scrollArea.setWidget(self.scrollAreaWidgetContents)
80 |
81 | # if it is the last widget of the veritical layout
82 | if self.verticalLayout.count()-1 == index:
83 |
84 | self.verticalLayout.addWidget(self.scrollArea)
85 |
86 | #if it isnt the last widget of the verticalLayout
87 | else:
88 | insert_into(self.verticalLayout,index+1,self.scrollArea)
89 | break
90 |
91 |
92 | # lbl =QtWidgets.QLabel(self.centralwidget)
93 |
94 | # self.verticalLayout.addWidget(lbl)
95 |
96 | btn.setText("Hide details")
97 |
98 | elif text=='Hide details':
99 | self.lbl_details=None
100 | btn.setText("Show details")
101 | for index in range(self.verticalLayout.count()):
102 | item = self.verticalLayout.itemAt(index).widget()
103 | if 'QScrollArea' in item.__str__():
104 | item.deleteLater()
105 | item.setParent(None)
106 | #delete the child widget of the QScrollArea
107 | item.widget().deleteLater()
108 | break
109 | else:
110 | print("unknown text in btn_show_details")
111 |
112 | def update_lbl_details(self,text:str):
113 | ''' updates the lbl_details adds the the string to the pre existing string in lbl_details
114 | This doesnt add new lines
115 | '''
116 |
117 | pre_text=self.lbl_details_text
118 | self.lbl_details_text=pre_text+text
119 |
120 | if self.lbl_details is not None:
121 | self.lbl_details.setText(self.lbl_details_text)
122 |
123 |
124 | if __name__=="__main__":
125 | global APP
126 | APP = QtWidgets.QApplication([])
127 |
128 | window = ProgressUi()
129 | window.show()
130 | APP.exec_()
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/grubEditor/widgets/ui/error_dialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 646
10 | 397
11 |
12 |
13 |
14 | Dialog
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 | <p>An error has occured.Please consider reporting this to <span><a href="https://github.com/Thenujan-0/grub-editor/issues">github page</a></span></p>
23 |
24 |
25 | true
26 |
27 |
28 | true
29 |
30 |
31 | Qt::LinksAccessibleByMouse
32 |
33 |
34 |
35 | -
36 |
37 |
38 | true
39 |
40 |
41 |
42 |
43 | 0
44 | 0
45 | 624
46 | 302
47 |
48 |
49 |
50 |
-
51 |
52 |
53 | Another error occured while loading the error message :)
54 |
55 |
56 | Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | -
65 |
66 |
-
67 |
68 |
69 | Qt::Horizontal
70 |
71 |
72 |
73 | 40
74 | 20
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 | Copy error message
83 |
84 |
85 |
86 | -
87 |
88 |
89 | OK
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/grubEditor/widgets/ui/view_snapshot.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 377
10 | 169
11 |
12 |
13 |
14 | Dialog
15 |
16 |
17 | -
18 |
19 |
20 | 10
21 |
22 |
-
23 |
24 |
25 | How do you want to view the file?
26 |
27 |
28 |
29 | -
30 |
31 |
32 | Do this everytime
33 |
34 |
35 |
36 | -
37 |
38 |
39 | On the appliction itself
40 |
41 |
42 |
43 | -
44 |
45 |
46 | Default text editor
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/grubEditor/widgets/view_mode_popup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import subprocess
3 |
4 | from PyQt5 import uic
5 | from PyQt5.QtWidgets import QDesktopWidget, QDialog
6 |
7 |
8 | PATH = os.path.dirname(os.path.realpath(__file__))
9 |
10 | class ViewModePopup(QDialog):
11 | def __init__(self,file_location,conf_handler,parent):
12 | self.file_location = file_location
13 | self.conf_handler = conf_handler
14 | self.parent = parent
15 | super(ViewModePopup, self).__init__(parent)
16 | uic.loadUi(f'{PATH}/ui/view_snapshot.ui',self)
17 | self.btn_on_the_application_itself.clicked.connect(self.btn_on_the_application_itself_callback)
18 | self.btn_default_text_editor.clicked.connect(self.btn_default_text_editor_callback)
19 |
20 | #create window in the center of the screen
21 | qtRectangle = self.frameGeometry()
22 | centerPoint = QDesktopWidget().availableGeometry().center()
23 | qtRectangle.moveCenter(centerPoint)
24 | self.move(qtRectangle.topLeft())
25 |
26 | def safe_close(self,arg):
27 | """makes sure the properties of checkbox is saved"""
28 | if self.checkBox_do_this_everytime.isChecked():
29 | set_preference('view_default',arg)
30 | self.close()
31 |
32 | def btn_default_text_editor_callback(self):
33 | self.safe_close('default_text_editor')
34 | subprocess.Popen([f'xdg-open \'{self.file_location}\''],shell=True)
35 |
36 | def btn_on_the_application_itself_callback(self):
37 | self.conf_handler.current_file= self.file_location
38 | self.parent.setUiElements(show_issues=False)
39 | self.parent.tabWidget.setCurrentIndex(0)
40 | self.safe_close('on_the_application_itself')
--------------------------------------------------------------------------------
/screenshots/grub-editor0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Thenujan-0/grub-editor/2cfa9a263dcb5522f2cccb29a910de1b29858040/screenshots/grub-editor0.png
--------------------------------------------------------------------------------
/screenshots/grub-editor1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Thenujan-0/grub-editor/2cfa9a263dcb5522f2cccb29a910de1b29858040/screenshots/grub-editor1.png
--------------------------------------------------------------------------------
/screenshots/grub-editor2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Thenujan-0/grub-editor/2cfa9a263dcb5522f2cccb29a910de1b29858040/screenshots/grub-editor2.png
--------------------------------------------------------------------------------
/screenshots/light-screenshot0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Thenujan-0/grub-editor/2cfa9a263dcb5522f2cccb29a910de1b29858040/screenshots/light-screenshot0.png
--------------------------------------------------------------------------------
/screenshots/light-screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Thenujan-0/grub-editor/2cfa9a263dcb5522f2cccb29a910de1b29858040/screenshots/light-screenshot1.png
--------------------------------------------------------------------------------
/tests/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | qt_api=pyqt5
--------------------------------------------------------------------------------
/tests/test_edit_configurations.py:
--------------------------------------------------------------------------------
1 | from calendar import c
2 | import os;
3 | import sys;
4 | from time import sleep
5 | from PyQt5 import QtWidgets,QtCore
6 | import subprocess
7 | from tools import change_comboBox_current_index ,create_tmp_file,create_snapshot
8 |
9 | HOME =os.getenv('HOME')
10 | PATH=os.path.dirname(os.path.realpath(__file__))
11 |
12 | #parent dir
13 | PATH=PATH[0:-5] + "/grubEditor"
14 | sys.path.append(PATH)
15 |
16 | import main
17 |
18 |
19 | def test_grub_timeout_add_substract(qtbot):
20 |
21 | MainWindow = main.Ui()
22 | mw=MainWindow
23 | qtbot.addWidget(mw)
24 |
25 | time_out_val =mw.ledit_grub_timeout.text()
26 | print(time_out_val,'default grub_timeout_val')
27 |
28 | # click in the Greet button and make sure it updates the appropriate label
29 | qtbot.mouseClick(mw.btn_add, QtCore.Qt.LeftButton)
30 | assert mw.ledit_grub_timeout.text() == str(float(time_out_val)+1)
31 | qtbot.mouseClick(mw.btn_substract, QtCore.Qt.LeftButton)
32 | assert mw.ledit_grub_timeout.text()==str(float(time_out_val))
33 |
34 | #set the value of ledit_grub_timeout to 1 and then check if it goes below zero
35 | mw.ledit_grub_timeout.setText("1.0")
36 | qtbot.mouseClick(mw.btn_substract, QtCore.Qt.LeftButton)
37 | assert mw.ledit_grub_timeout.text() == "0.0"
38 | qtbot.mouseClick(mw.btn_substract, QtCore.Qt.LeftButton)
39 | assert mw.ledit_grub_timeout.text() == "0.0"
40 |
41 | def test_look_for_other_os(qtbot):
42 | MainWindow = main.Ui()
43 | mw=MainWindow
44 | qtbot.addWidget(mw)
45 |
46 | cb =mw.checkBox_look_for_other_os
47 |
48 |
49 | if not cb.isChecked():
50 | main.initialize_temp_file()
51 | main.set_value("GRUB_DISABLE_OS_PROBER=","false")
52 | mw.saveConfs()
53 |
54 |
55 | #h
56 | with qtbot.waitSignal(mw.saveConfs_worker.signals.finished,timeout=30*1000):
57 | pass
58 |
59 |
60 |
61 |
62 |
63 | mw.setUiElements()
64 |
65 |
66 | assert cb.isChecked() ==True
67 | assert not mw.btn_set.isEnabled()
68 |
69 | # mw.checkBox_look_for_other_os.setChecked(False)
70 | cb.setChecked(False)
71 | cb.clicked.emit()
72 |
73 | assert cb.isChecked() ==False
74 |
75 | assert mw.btn_set.isEnabled()
76 |
77 | qtbot.mouseClick(mw.btn_set, QtCore.Qt.LeftButton)
78 | assert mw.lbl_status.text() =="Waiting for authentication"
79 |
80 | while mw.lbl_status.text() =="Waiting for authentication":
81 | sleep(1)
82 |
83 | assert mw.lbl_status.text() =="Saving configurations"
84 | while mw.lbl_status.text()=="Saving configurations":
85 | sleep(1)
86 |
87 |
88 |
89 | issues=[]
90 | assert main.get_value("GRUB_DISABLE_OS_PROBER=",issues)=="true"
91 | assert issues ==[]
92 |
93 | assert mw.lbl_status.text() =="Saved successfully"
94 |
95 |
96 |
97 | #repeat the same test but now the opposite case
98 |
99 | cb=mw.checkBox_look_for_other_os
100 | cb.setChecked(True)
101 |
102 | assert cb.isChecked() ==True
103 |
104 | main.set_value("GRUB_DISABLE_OS_PROBER=","true")
105 |
106 | qtbot.mouseClick(mw.btn_set, QtCore.Qt.LeftButton)
107 | assert mw.lbl_status.text() =="Waiting for authentication"
108 | while mw.lbl_status.text()=='Waiting for authentication':
109 | sleep(1)
110 | assert mw.lbl_status.text() =="Saving configurations"
111 |
112 | while mw.lbl_status.text()=="Saving configurations":
113 | sleep(1)
114 |
115 |
116 |
117 | issues=[]
118 | assert main.get_value("GRUB_DISABLE_OS_PROBER=",issues)=="false"
119 | assert issues ==[]
120 |
121 |
122 | assert mw.lbl_status.text() =="Saved successfully"
123 |
124 | def test_comboBox_configurations(qtbot):
125 | mw = main.Ui()
126 | main.MainWindow=mw
127 | qtbot.addWidget(mw)
128 | for i in range(len(mw.all_entries)):
129 | if i != mw.comboBox_grub_default.currentIndex():
130 | temp_entry = mw.all_entries[i]
131 | break
132 |
133 |
134 | snapshot_test="""GRUB_DEFAULT="""
135 | print(f' echo "{snapshot_test}" > {main.DATA_LOC}/snapshots/test_snapshot')
136 | subprocess.run([f' echo "{snapshot_test}" > {main.DATA_LOC}/snapshots/test_snapshot'],shell=True)
137 | main.set_value("GRUB_DEFAULT=",temp_entry,target_file=f"{main.DATA_LOC}/snapshots/test_snapshot")
138 | mw.setUiElements(only_snapshots=True)
139 | mw.comboBox_configurations.setCurrentIndex(mw.configurations.index("test_snapshot"))
140 |
141 |
142 | #check if the correct value for grub default was shown
143 | assert mw.all_entries[mw.comboBox_grub_default.currentIndex()] ==temp_entry
144 |
145 |
146 |
147 | def test_btn_set(qtbot):
148 | mw = main.Ui()
149 | main.MainWindow=mw
150 | qtbot.addWidget(mw)
151 | assert "(modified)" not in mw.configurations[mw.comboBox_configurations.currentIndex()]
152 | old_ind=mw.comboBox_grub_default.currentIndex()
153 |
154 | curr_ind =change_comboBox_current_index(mw)
155 | print(curr_ind,"currentIndex")
156 | new_ind=mw.comboBox_grub_default.currentIndex()
157 |
158 | assert new_ind!=old_ind
159 |
160 | assert "(modified)" in mw.configurations[mw.comboBox_configurations.currentIndex()]
161 | qtbot.mouseClick(mw.btn_set, QtCore.Qt.LeftButton)
162 |
163 | with qtbot.waitSignal(mw.saveConfs_worker.signals.finished,raising=True,timeout=30*1000):
164 | pass
165 |
166 | assert mw.original_modifiers ==[]
167 | print(mw.configurations[mw.comboBox_configurations.currentIndex()])
168 | assert "(modified)" not in mw.configurations[mw.comboBox_configurations.currentIndex()]
169 |
170 | def test_checkBox_look_for_other_os(qtbot):
171 | ''' Test if this comboBox defaults to not checked if GRUB_DISABLE_OS_PROBER wasn't found or commented '''
172 | mw=main.Ui()
173 | main.MainWindow=mw
174 | qtbot.addWidget(mw)
175 |
176 | test_config1="""#GRUB_DISABLE_OS_PROBER=false"""
177 |
178 |
179 | tmp_file=create_tmp_file(test_config1)
180 | issues=[]
181 | val =main.get_value("GRUB_DISABLE_OS_PROBER=",issues,tmp_file)
182 | assert val =="true"
183 | assert issues ==[f"GRUB_DISABLE_OS_PROBER= is commented out in {tmp_file}"]
184 |
185 | def test_comboBox_grub_default_numbers(qtbot):
186 | mw = main.Ui()
187 | main.MainWindow=mw
188 | qtbot.addWidget(mw)
189 |
190 | #close that dialog if it exists because it poped up because /etc/default/grub has an invalid entry
191 | if mw.dialog_invalid_default_entry:
192 | mw.dialog_invalid_default_entry.close()
193 |
194 |
195 | test_config1="""GRUB_DEFAULT=\"1\""""
196 |
197 | sfile=create_snapshot(test_config1)
198 | mw.setUiElements(only_snapshots=True)
199 | mw.comboBox_configurations.setCurrentIndex(mw.configurations.index(sfile))
200 |
201 | assert mw.all_entries[mw.comboBox_grub_default.currentIndex()]==mw.all_entries[1]
202 |
203 |
204 | #todo test 0 >2
205 | #1 >2
206 | test_config2="GRUB_DEFAULT=0>1"
207 | sfile=create_snapshot(test_config2)
208 | mw.setUiElements()
209 |
210 | mw.comboBox_configurations.setCurrentIndex(mw.configurations.index(sfile))
211 |
212 | assert mw.dialog_invalid_default_entry.isVisible()
213 | assert mw.comboBox_grub_default.styleSheet()==mw.comboBox_grub_default_invalid_style
214 |
215 | mw.dialog_invalid_default_entry.close()
216 | assert not mw.dialog_invalid_default_entry.isVisible()
217 |
218 | test_config3="GRUB_DEFAULT=\"1>1\""
219 | sfile=create_snapshot(test_config3)
220 | mw.setUiElements()
221 | mw.dialog_invalid_default_entry.close()
222 |
223 |
224 | mw.comboBox_configurations.setCurrentIndex(mw.configurations.index(sfile))
225 | assert not mw.dialog_invalid_default_entry.isVisible()
226 | assert mw.comboBox_grub_default.currentIndex()==2
227 |
228 |
229 |
230 | test_config4="GRUB_DEFAULT=0"
231 | sfile=create_snapshot(test_config4)
232 | mw.setUiElements()
233 | mw.comboBox_configurations.setCurrentIndex(mw.configurations.index(sfile))
234 | assert not mw.dialog_invalid_default_entry.isVisible()
235 | assert mw.comboBox_grub_default.currentIndex()==0
236 |
237 |
238 | def test_missing_double_quotes_default(qtbot):
239 | mw = main.Ui()
240 | main.MainWindow=mw
241 | qtbot.addWidget(mw)
242 |
243 | test_config="""GRUB_DEFAULT=Manjaro Linux
244 | GRUB_TIMEOUT=20
245 | GRUB_TIMEOUT_STYLE=menu
246 | GRUB_DISTRIBUTOR=\"Manjaro\""""
247 |
248 | sfile=create_snapshot(test_config)
249 | mw.setUiElements()
250 | if mw.dialog_invalid_default_entry:
251 | mw.dialog_invalid_default_entry.close()
252 |
253 | mw.comboBox_configurations.setCurrentIndex(mw.configurations.index(sfile))
254 |
255 | assert mw.comboBox_grub_default.styleSheet()==mw.comboBox_grub_default_invalid_style
256 | assert mw.dialog_invalid_default_entry.isVisible()
257 |
--------------------------------------------------------------------------------
/tests/test_fix_kernel_version.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import re
4 | import subprocess
5 | from time import sleep
6 | from pathlib import Path
7 | from PyQt5 import QtWidgets,QtCore,QtTest
8 | from tools import change_comboBox_current_index,delete_pref
9 |
10 |
11 | PATH = os.path.dirname(os.path.realpath(__file__))
12 | #get the parent directory
13 | PATH = Path(PATH).parent
14 | sys.path.append(PATH)
15 |
16 | import main
17 |
18 | HOME=os.getenv("HOME")
19 |
20 | def change_krnl_minor_vrsn(val)->str:
21 | #match the 14-1- like part of 5.16.14-1-
22 | pattern=r'\d+-\d+-'
23 |
24 | krnl_minor_vrsn=re.search(pattern,val).group(0)
25 | print(val)
26 | print(krnl_minor_vrsn)
27 |
28 |
29 | ind =krnl_minor_vrsn.find("-")
30 | krnl_minor_vrsn_strp=krnl_minor_vrsn[:ind]
31 | # new_ind = krnl_minor_vrsn[ind+1:].find("-") +ind
32 | # krnl_minor_vrsn = krnl_minor_vrsn[]
33 |
34 | print(krnl_minor_vrsn_strp)
35 | new_krnl_minor_vrsn_strp = str(int(krnl_minor_vrsn_strp)+1)
36 | new_krnl_minor_vrsn= krnl_minor_vrsn.replace(krnl_minor_vrsn_strp,new_krnl_minor_vrsn_strp)
37 | print(new_krnl_minor_vrsn)
38 | new_val = val.replace(krnl_minor_vrsn,new_krnl_minor_vrsn)
39 | print(new_val)
40 | return new_val
41 |
42 | def test_fix_kernel_version_conf(qtbot):
43 | mw=main.Ui()
44 | main.MainWindow=mw
45 | if len(mw.invalid_entries)==0:
46 | for val in mw.all_entries:
47 | if " >" not in val:
48 | continue
49 | new_val=change_krnl_minor_vrsn(val)
50 |
51 |
52 | break
53 | print("changing the current entry to invalid")
54 | mw.close()
55 | main.initialize_temp_file()
56 | main.set_value("GRUB_DEFAULT=",new_val)
57 | subprocess.run([f"pkexec cp {HOME}/.cache/grub-editor/temp.txt /etc/default/grub"]
58 | ,shell=True)
59 | mw = main.Ui()
60 | main.MainWindow=mw
61 |
62 | assert mw.dialog_invalid_default_entry.isVisible()
63 |
64 | #First press the cancel button check if window closes and
65 | # no changes were made to the snapshot/file
66 | print(main.file_loc)
67 | old_sum = subprocess.check_output([f"sha256sum {main.file_loc}"],shell=True).decode()
68 |
69 | qtbot.mouseClick(mw.dialog_invalid_default_entry.btn_cancel,QtCore.Qt.LeftButton)
70 |
71 | new_sum =subprocess.check_output([f"sha256sum {main.file_loc}"],shell=True).decode()
72 | assert old_sum ==new_sum
73 |
74 | assert not mw.dialog_invalid_default_entry.isVisible()
75 |
76 |
77 | #Now lets press the fix button and check if it actually fixes the entry
78 |
79 | #first we need to call setUiElements to reshow that dialog
80 | mw.setUiElements(show_issues=True)
81 |
82 | assert mw.dialog_invalid_default_entry.isVisible()
83 |
84 | assert mw.comboBox_grub_default.currentIndex() == len(mw.all_entries)-1
85 |
86 | qtbot.mouseClick(mw.dialog_invalid_default_entry.btn_ok, QtCore.Qt.LeftButton)
87 |
88 | assert not mw.dialog_invalid_default_entry.isVisible()
89 | issues=[]
90 | with qtbot.waitSignal(mw.saveConfs_worker.signals.finished,timeout=30*1000):
91 | pass
92 | new_default_val = main.get_value("GRUB_DEFAULT=",issues)
93 |
94 | assert not issues
95 | assert new_default_val in mw.all_entries
96 | assert new_default_val not in mw.invalid_entries
97 |
98 |
99 |
100 |
101 |
102 |
103 | def test_fix_kernel_version_snapshot(qtbot):
104 | mw=main.Ui()
105 | main.MainWindow=mw
106 |
107 | if len(mw.invalid_entries)!=0:
108 | mw.dialog_invalid_default_entry.close()
109 |
110 | ind=2
111 | mw.comboBox_configurations.setCurrentIndex(2)
112 | #a snapshot would be selected now
113 |
114 |
115 |
116 | for val in mw.all_entries:
117 | if " >" not in val:
118 | continue
119 |
120 | new_val=change_krnl_minor_vrsn(val)
121 | break
122 | print("changing the current entry to invalid")
123 | mw.close()
124 | main.initialize_temp_file()
125 | main.set_value("GRUB_DEFAULT=",new_val)
126 | print(main.file_loc)
127 | subprocess.run([f"cp {HOME}/.cache/grub-editor/temp.txt {main.file_loc}"],shell=True)
128 | mw = main.Ui()
129 | main.MainWindow=mw
130 | mw.comboBox_configurations.setCurrentIndex(ind)
131 |
132 | assert mw.dialog_invalid_default_entry.isVisible()
133 |
134 | #First press the cancel button check if window closes and no changes were made to the snapshot/file
135 | print(main.file_loc,ind)
136 |
137 | old_sum = subprocess.check_output([f"sha256sum {main.file_loc}"],shell=True).decode()
138 |
139 | qtbot.mouseClick(mw.dialog_invalid_default_entry.btn_cancel,QtCore.Qt.LeftButton)
140 |
141 | new_sum =subprocess.check_output([f"sha256sum {main.file_loc}"],shell=True).decode()
142 | assert old_sum ==new_sum
143 |
144 | assert not mw.dialog_invalid_default_entry.isVisible()
145 |
146 |
147 | #Now lets press the fix button and check if it actually fixes the entry
148 |
149 | #first we need to call setUiElements to reshow that dialog
150 | mw.setUiElements(show_issues=True)
151 |
152 | assert mw.dialog_invalid_default_entry.isVisible()
153 |
154 | assert mw.comboBox_grub_default.currentIndex() == len(mw.all_entries)-1
155 |
156 | qtbot.mouseClick(mw.dialog_invalid_default_entry.btn_ok, QtCore.Qt.LeftButton)
157 |
158 | assert not mw.dialog_invalid_default_entry.isVisible()
159 | issues=[]
160 |
161 | new_default_val = main.get_value("GRUB_DEFAULT=",issues)
162 |
163 | assert not issues
164 | assert new_default_val in mw.all_entries
165 | assert new_default_val not in mw.invalid_entries
166 |
167 |
168 | def test_do_this_everytime(qtbot):
169 |
170 |
171 |
172 | mw = main.Ui()
173 | main.MainWindow=mw
174 |
175 | ind=2
176 | # -1 because lines has the list of snapshots but the configurations has /etc/default/grub
177 | target_snapshot=mw.lines[ind-1]
178 | target_snap_path=f'{main.DATA_LOC}/snapshots/{target_snapshot}'
179 | for val in mw.all_entries:
180 | if " >" not in val:
181 | continue
182 |
183 | new_val=change_krnl_minor_vrsn(val)
184 | break
185 | print("changing the current entry to invalid",new_val,target_snap_path)
186 | mw.close()
187 | main.set_value("GRUB_DEFAULT=",new_val,target_snap_path)
188 | mw = main.Ui()
189 | main.MainWindow=mw
190 | delete_pref()
191 | if len(mw.invalid_entries)!=0:
192 | mw.dialog_invalid_default_entry.close()
193 |
194 | pref=main.get_preference("invalid_kernel_version")
195 | assert pref is None
196 | mw.comboBox_configurations.setCurrentIndex(ind)
197 |
198 | assert mw.dialog_invalid_default_entry.isVisible()
199 |
200 | dialog =mw.dialog_invalid_default_entry
201 | QtTest.QTest.qWait(1000)
202 | print('done 1')
203 | pref=main.get_preference("invalid_kernel_version")
204 | assert pref is None
205 |
206 | if not dialog.checkBox.isChecked():
207 | mw.dialog_invalid_default_entry.checkBox.click()
208 |
209 | pref=main.get_preference("invalid_kernel_version")
210 | assert pref is None
211 |
212 | qtbot.mouseClick(dialog.btn_cancel, QtCore.Qt.LeftButton)
213 |
214 | # QtTest.QTest.qWait(10000)
215 | pref=main.get_preference("invalid_kernel_version")
216 | assert pref == "cancel"
217 |
218 | assert not dialog.isVisible()
219 |
220 | #to reshow the dialog
221 | mw.setUiElements()
222 |
223 | #close the dialog if it popped up for /etc/default/grub entry
224 | if len(mw.invalid_entries)>0:
225 | mw.dialog_invalid_default_entry.close()
226 |
227 | mw.comboBox_configurations.setCurrentIndex(ind)
228 |
229 | dialog=mw.dialog_invalid_default_entry
230 | assert not dialog.isVisible()
231 |
232 |
233 |
--------------------------------------------------------------------------------
/tests/test_get_set_value.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 | import sys
4 | from PyQt5 import QtWidgets,QtCore
5 | from tools import create_tmp_file
6 |
7 |
8 | from grubEditor import main
9 |
10 | commented_config="""#GRUB_DEFAULT="Manjaro Linux"
11 | #GRUB_TIMEOUT=20
12 | #GRUB_TIMEOUT_STYLE=menu
13 | #GRUB_DISTRIBUTOR="Manjaro"
14 | #GRUB_CMDLINE_LINUX_DEFAULT="quiet apparmor=1 security=apparmor udev.log_priority=3"
15 | #GRUB_CMDLINE_LINUX=""
16 |
17 | # If you want to enable the save default function, uncomment the following
18 | # line, and set GRUB_DEFAULT to saved.
19 | GRUB_SAVEDEFAULT=true
20 |
21 | # Preload both GPT and MBR modules so that they are not missed
22 | GRUB_PRELOAD_MODULES="part_gpt part_msdos"
23 |
24 |
25 |
26 | # Uncomment to ensure that the root filesystem is mounted read-only so that
27 | # systemd-fsck can run the check automatically. We use 'fsck' by default, which
28 | # needs 'rw' as boot parameter, to avoid delay in boot-time. 'fsck' needs to be
29 | # removed from 'mkinitcpio.conf' to make 'systemd-fsck' work.
30 | # See also Arch-Wiki: https://wiki.archlinux.org/index.php/Fsck#Boot_time_checking
31 | #GRUB_ROOT_FS_RO=true
32 |
33 | """
34 |
35 |
36 |
37 | PATH= os.path.dirname(os.path.realpath(__file__))
38 | HOME=os.getenv('HOME')
39 |
40 |
41 |
42 |
43 | def test_commented_lines(qtbot):
44 | tmp_file=create_tmp_file(commented_config)
45 | issues=[]
46 | val =main.conf_handler.get("GRUB_DEFAULT=",issues,tmp_file)
47 | assert val==None
48 | assert issues==[f"GRUB_DEFAULT= is commented out in {tmp_file}"]
49 |
50 | main.conf_handler.set("GRUB_DEFAULT=","Garuda Linux",tmp_file)
51 |
52 |
53 | issues=[]
54 | print(tmp_file)
55 | assert main.conf_handler.get("GRUB_DEFAULT=",issues,tmp_file)=='Garuda Linux'
56 |
57 |
58 | config_fake_comment="""
59 | GRUB_DEFAULT="Manjaro Linux"
60 | GRUB_TIMEOUT=20
61 | GRUB_TIMEOUT_STYLE=menu
62 | GRUB_DISTRIBUTOR="Manjaro"
63 | GRUB_CMDLINE_LINUX_DEFAULT="quiet apparmor=1 security=apparmor udev.log_priority=3"
64 | GRUB_CMDLINE_LINUX=""
65 | # setting 'GRUB_DEFAULT=saved'
66 | """
67 |
68 | #after means that fake comment comes after the actual value
69 | def test_fake_comment_after_get(qtbot):
70 | mw=main.Ui()
71 | main.MainWindow=mw
72 | qtbot.addWidget(mw)
73 |
74 | tmp_file=create_tmp_file(config_fake_comment)
75 |
76 | issues = []
77 | val = main.conf_handler.get("GRUB_DEFAULT=",issues,read_file=tmp_file)
78 | assert val == 'Manjaro Linux'
79 |
80 | assert not issues
81 |
82 |
83 |
84 | def test_quotation_marks_trailing_space(qtbot):
85 | test_config="GRUB_DEFAULT=\"0\" "
86 |
87 | tmp_file=create_tmp_file(test_config)
88 |
89 | issues=[]
90 | val = main.conf_handler.get("GRUB_DEFAULT=",issues,tmp_file)
91 | assert val=='0'
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | #before means that fake comment comes before the actual value
101 |
102 | config_fake_comment_before="""
103 | #GRUB_DEFAULT=0
104 | GRUB_DEFAULT="Manjaro Linux"
105 | GRUB_TIMEOUT_STYLE=menu
106 | GRUB_TIMEOUT=20
107 | GRUB_DISTRIBUTOR="Manjaro"
108 | GRUB_CMDLINE_LINUX_DEFAULT="quiet apparmor=1 security=apparmor udev.log_priority=3"
109 | GRUB_CMDLINE_LINUX=""
110 | # setting 'GRUB_DEFAULT=saved'
111 | """
112 |
113 | def test_fake_comment_before_get(qtbot):
114 | """ Test with a commented key value pair before the uncommented key value pair """
115 |
116 | tmp_file=f'{main.CACHE_LOC}/temp3.txt'
117 | subprocess.run([f'touch {tmp_file}'],shell=True)
118 |
119 | with open(tmp_file,'w') as f:
120 | f.write(config_fake_comment_before)
121 |
122 | issues=[]
123 | val =main.conf_handler.get("GRUB_DEFAULT=",issues,tmp_file)
124 | assert issues ==[]
125 | assert val == 'Manjaro Linux'
126 |
127 |
128 | config_last="""
129 | GRUB_DEFAULT="Manjaro Linux"
130 | GRUB_TIMEOUT=20
131 | GRUB_TIMEOUT_STYLE=menu"""
132 |
133 | def test_last_value(qtbot):
134 |
135 | tmp_file=f'{main.CACHE_LOC}/temp4.txt'
136 | subprocess.run([f'touch {tmp_file}'],shell=True)
137 |
138 | with open(tmp_file,'w') as f:
139 | f.write(config_last)
140 |
141 | issues=[]
142 | val =main.conf_handler.get("GRUB_TIMEOUT_STYLE=",issues,tmp_file)
143 | assert issues ==[]
144 | assert val=="menu"
145 |
146 | def test_not_in_conf_val(qtbot):
147 | ''' Looking for a value that is not mentioned in the configuration '''
148 |
149 | tmp_file=f'{main.CACHE_LOC}/temp5.txt'
150 | subprocess.run([f'touch {tmp_file}'],shell=True)
151 |
152 | with open(tmp_file,'w') as f:
153 | f.write(config_last)
154 | issues=[]
155 |
156 | val =main.conf_handler.get("GRUB_CMDLINE_LINUX=",issues,tmp_file)
157 | assert issues ==[f"GRUB_CMDLINE_LINUX= was not found in {tmp_file}"]
158 | assert val == None
159 |
160 | main.conf_handler.set("GRUB_CMDLINE_LINUX=","something fake",tmp_file)
161 | issues=[]
162 | new_val = main.conf_handler.get("GRUB_CMDLINE_LINUX=",issues,tmp_file)
163 | assert new_val =="something fake"
164 | assert issues==[]
165 |
166 | def test_missing_double_quotes(qtbot):
167 | config="GRUB_DEFAULT=Manjaro Linux"
168 | tmp_file=create_tmp_file(config)
169 |
170 | issues=[]
171 | val =main.conf_handler.get("GRUB_DEFAULT=",issues,tmp_file)
172 |
173 | assert issues == []
174 | assert val == "Manjaro Linux (Missing \")"
175 |
176 |
177 | def test_grub_default_saved():
178 | config="GRUB_DEFAULT=saved"
179 | tmp_file=create_tmp_file(config)
180 |
181 | issues=[]
182 | val = main.conf_handler.get("GRUB_DEFAULT=",issues,tmp_file)
183 | assert val=="saved"
184 | assert issues==[]
185 |
186 |
187 | CONFIG_QUOTED ="""
188 | GRUB_DEFAULT="Manjaro Linux" """
189 |
190 | def test_remove_quotes(qtbot):
191 | tmp_file = create_tmp_file(CONFIG_QUOTED)
192 | issues = []
193 | val = main.conf_handler.get("GRUB_DEFAULT=",issues,tmp_file,remove_quotes_=True)
194 | assert val == "Manjaro Linux"
195 | val = main.conf_handler.get("GRUB_DEFAULT=",issues,tmp_file,remove_quotes_=False)
196 | assert val == "\"Manjaro Linux\""
197 |
198 |
--------------------------------------------------------------------------------
/tests/test_grubcfg_error.py:
--------------------------------------------------------------------------------
1 |
2 | import subprocess
3 | from time import sleep
4 |
5 | from PyQt5 import QtCore
6 |
7 | from grubEditor import main
8 | from grubEditor.libs import find_entries
9 |
10 |
11 |
12 | def test_grub_cfg_not_found(qtbot):
13 |
14 | #change the GRUB_CONF to a non-existing file so that not found error will be raised
15 | find_entries.GRUB_CONF_NONEDITABLE="/boot/grub/grub1.cfg"
16 |
17 |
18 |
19 | mw=main.Ui()
20 | main.MainWindow=mw
21 | qtbot.addWidget(mw)
22 |
23 | assert mw.dialog_grub_cfg_not_found.isVisible()
24 |
25 | find_entries.GRUB_CONF="/boot/grub/grub.cfg"
26 |
27 | qtbot.mouseClick(mw.dialog_grub_cfg_not_found.btn_ok, QtCore.Qt.LeftButton)
28 |
29 | assert not mw.dialog_grub_cfg_not_found.isVisible()
30 |
31 |
32 |
33 | def test_grub_cfg_permission(qtbot):
34 |
35 | subprocess.run(['pkexec chmod 600 /boot/grub/grub.cfg'],shell=True)
36 |
37 | mw=main.Ui()
38 | main.MainWindow=mw
39 | qtbot.addWidget(mw)
40 |
41 | sleep(1)
42 | assert mw.dialog_cfg_permission.isVisible()
43 |
44 | subprocess.run(['pkexec chmod 644 /boot/grub/grub.cfg'],shell=True)
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/tests/test_invalid_default_entry.py:
--------------------------------------------------------------------------------
1 |
2 | import os
3 | import sys
4 |
5 | from PyQt5 import QtWidgets,QtCore
6 |
7 | from grubEditor import main
8 |
9 | def test_invalid_default_entry(qtbot):
10 | mw=main.Ui()
11 | main.MainWindow=mw
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/test_output_widget.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | from time import sleep
4 | from PyQt5 import QtWidgets,QtCore,QtTest
5 | from tools import *
6 | from grubEditor import main
7 |
8 | def test_no_show_details(qtbot):
9 | """ Test the usage of show_details btn when update-grub is performed """
10 |
11 | mw=main.Ui()
12 | main.MainWindow=mw
13 | main.DEBUG=True
14 |
15 | qtbot.mouseClick(mw.btn_add,QtCore.Qt.LeftButton)
16 | assert not scrollArea_visible(mw)
17 |
18 | #click btn set and check scroll area part is invisible
19 | qtbot.mouseClick(mw.btn_set,QtCore.Qt.LeftButton)
20 | assert not scrollArea_visible(mw)
21 |
22 | while password_not_entered(mw):
23 | sleep(1)
24 | print("count",mw.verticalLayout.count())
25 |
26 |
27 | #click show details and check if scroll area is visible
28 | qtbot.mouseClick(mw.btn_show_details,QtCore.Qt.LeftButton)
29 | assert scrollArea_visible(mw)
30 |
31 | #Now click hide details button and check if scroll area is invisible
32 | qtbot.mouseClick(mw.btn_show_details,QtCore.Qt.LeftButton)
33 | assert not scrollArea_visible(mw)
34 |
35 | QtTest.QTest.qWait(1000)
36 |
37 | #Now repeat the same process again
38 | #click show details and check if scroll area is visible
39 | qtbot.mouseClick(mw.btn_show_details,QtCore.Qt.LeftButton)
40 | assert scrollArea_visible(mw)
41 |
42 | #Now click hide details button and check if scroll area is invisible
43 | qtbot.mouseClick(mw.btn_show_details,QtCore.Qt.LeftButton)
44 | assert not scrollArea_visible(mw)
45 |
--------------------------------------------------------------------------------
/tests/test_progress.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import os
3 | import sys
4 | from time import sleep
5 | from PyQt5 import QtWidgets,QtCore
6 | from tools import *
7 |
8 | from grubEditor.widgets.progress import ProgressUi
9 |
10 | def test_btn_show_details(qtbot):
11 | #mw is mainWindow
12 | mw=ProgressUi()
13 | qtbot.addWidget(mw)
14 |
15 | #click btn show details and check if scroll area is shown
16 | qtbot.mouseClick(mw.btn_show_details, QtCore.Qt.LeftButton)
17 | assert scrollArea_visible(mw,mw.verticalLayout)
18 |
19 | #check if btn show details is now named to hide details
20 | assert mw.verticalLayout.itemAt(2).widget().text()=='Hide details'
21 |
22 | #click hide details and check if it works
23 | qtbot.mouseClick(mw.btn_show_details,QtCore.Qt.LeftButton)
24 | assert mw.verticalLayout.itemAt(2).widget().text()=='Show details'
25 | assert None == mw.verticalLayout.itemAt(3)
26 |
27 |
28 |
29 | # add another button to the window and then check if QScrollArea is created in right
30 | # place when btn_show_details is pressed
31 | btn_close =QtWidgets.QPushButton()
32 | btn_close.setText("Close")
33 | def btn_close_callback():
34 | print("this button isnt supposed to work")
35 |
36 | btn_close.clicked.connect(btn_close_callback)
37 | mw.verticalLayout.addWidget(btn_close)
38 |
39 |
40 | vcount =mw.verticalLayout.count()
41 | #check if last widget is the close button
42 | assert mw.verticalLayout.itemAt(vcount-1).widget().text() == 'Close'
43 |
44 |
45 | #now lets press the button
46 | qtbot.mouseClick(mw.btn_show_details,QtCore.Qt.LeftButton)
47 |
48 | #check if QScrollArea has been created in 3rd index
49 | assert scrollArea_visible(mw,mw.verticalLayout)
50 |
51 | line1="just pretend this is the first line of something important"
52 |
53 | mw.update_lbl_details(line1)
54 |
55 | assert mw.lbl_details_text ==line1
56 |
57 | assert mw.lbl_details.text()==line1
58 |
59 | line2="\npretent that this is the second line of some big text"
60 | mw.update_lbl_details(line2)
61 |
62 | assert mw.lbl_details_text ==line1+line2
63 | assert mw.lbl_details.text()==line1+line2
64 |
65 | #click hide details button and check if lbl_details_text percists
66 | qtbot.mouseClick(mw.btn_show_details,QtCore.Qt.LeftButton)
67 |
68 | assert mw.lbl_details_text ==line1+line2
69 |
70 | #now click show details and check if lbl_details has the text that it is supposed to have
71 | qtbot.mouseClick(mw.btn_show_details,QtCore.Qt.LeftButton)
72 | assert mw.lbl_details.text()==line1+line2
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/tests/test_quotes_values.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | from tools import create_tmp_file,create_snapshot
4 |
5 | from grubEditor.core import GRUB_CONF
6 | from grubEditor import main
7 |
8 | QUOTED_GRUB_TIMEOUT="""
9 | GRUB_DEFAULT="Manjaro Linux"
10 | GRUB_TIMEOUT="-1"
11 | GRUB_TIMEOUT_STYLE=menu
12 | GRUB_DISTRIBUTOR="Manjaro"
13 | GRUB_CMDLINE_LINUX_DEFAULT="quiet apparmor=1 security=apparmor udev.log_priority=3"
14 | GRUB_CMDLINE_LINUX=""
15 | # """
16 |
17 |
18 | def test_quoted_grub_timeout(qtbot):
19 | mw = main.Ui()
20 | main.MainWindow=mw
21 | qtbot.addWidget(mw)
22 | FILE_PATH = create_tmp_file(QUOTED_GRUB_TIMEOUT)
23 | main.conf_handler.current_file = FILE_PATH
24 |
25 | mw.setUiElements()
26 | val = main.conf_handler.get(GRUB_CONF.GRUB_TIMEOUT,[])
27 | assert val == "\"-1\""
28 | assert not mw.checkBox_boot_default_entry_after.isChecked()
--------------------------------------------------------------------------------
/tests/test_reinstall_grub_package.py:
--------------------------------------------------------------------------------
1 | # import unittest
2 |
3 | # import os,sys,inspect
4 |
5 |
6 | # #stolen from stackoverflow or grepperchrome
7 | # currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
8 | # parentdir = os.path.dirname(currentdir)
9 | # sys.path.insert(0,parentdir)
10 |
11 |
12 | # import main
13 | # import threading
14 | # from PyQt5 import QtWidgets,QtCore
15 | # import sys
16 | # from time import sleep
17 | # import pytest
18 |
19 |
20 |
21 | # def test_btn_reinstall_grub(qtbot):
22 | # main.MainWindow=main.Ui()
23 | # mw=main.MainWindow
24 | # mw.tabWidget.setCurrentIndex(2)
25 |
26 |
27 | # lv=mw.chroot.listWidget
28 | # item =lv.item(0)
29 | # rect = lv.visualItemRect(item)
30 | # center = rect.center()
31 | # print(center)
32 | # print(item.text())
33 | # assert lv.itemAt(center).text() == item.text()
34 | # # assert lv.currentRow() == 0
35 |
36 |
37 | # qtbot.mouseClick(mw.chroot.listWidget.viewport(),QtCore.Qt.LeftButton,pos=center)
38 | # # mw.chroot.listWidget.item(2).click()
39 | # assert type( mw.tabWidget.currentWidget()).__name__ =='ChrootAfterUi'
40 | # currentWidget=mw.tabWidget.currentWidget()
41 | # qtbot.mouseClick(currentWidget.btn_reinstall_grub_package,QtCore.Qt.LeftButton)
42 | # sleep(2)
43 |
44 |
--------------------------------------------------------------------------------
/tests/test_remove_value.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 | import sys
4 | from PyQt5 import QtWidgets,QtCore
5 | from pathlib import Path
6 |
7 | # PATH=os.path.dirname(os.path.realpath(__file__))
8 | # PARENT_PATH=str(Path(PATH).parent)
9 |
10 |
11 | # print(PARENT_PATH)
12 | # sys.path.append(PARENT_PATH)
13 |
14 | from grubEditor.core import CONF_HANDLER
15 | from tools import create_tmp_file,get_file_sum
16 | from grubEditor import main
17 | from grubEditor.locations import CACHE_LOC
18 |
19 | conf_handler = CONF_HANDLER()
20 | get_value = conf_handler.get
21 | set_value = conf_handler.set
22 | remove_value = conf_handler.remove
23 |
24 | commented_config="""#GRUB_DEFAULT="Manjaro Linux"
25 | #GRUB_TIMEOUT=20
26 | #GRUB_TIMEOUT_STYLE=menu
27 | #GRUB_DISTRIBUTOR="Manjaro"
28 | #GRUB_CMDLINE_LINUX_DEFAULT="quiet apparmor=1 security=apparmor udev.log_priority=3"
29 | #GRUB_CMDLINE_LINUX=""
30 |
31 | # If you want to enable the save default function, uncomment the following
32 | # line, and set GRUB_DEFAULT to saved.
33 | GRUB_SAVEDEFAULT=true
34 |
35 | # Preload both GPT and MBR modules so that they are not missed
36 | GRUB_PRELOAD_MODULES="part_gpt part_msdos"
37 |
38 |
39 |
40 | # Uncomment to ensure that the root filesystem is mounted read-only so that
41 | # systemd-fsck can run the check automatically. We use 'fsck' by default, which
42 | # needs 'rw' as boot parameter, to avoid delay in boot-time. 'fsck' needs to be
43 | # removed from 'mkinitcpio.conf' to make 'systemd-fsck' work.
44 | # See also Arch-Wiki: https://wiki.archlinux.org/index.php/Fsck#Boot_time_checking
45 | #GRUB_ROOT_FS_RO=true
46 |
47 | """
48 |
49 | PATH= os.path.dirname(os.path.realpath(__file__))
50 | HOME=os.getenv('HOME')
51 |
52 |
53 |
54 |
55 |
56 | def test_commented_lines(qtbot):
57 | tmp_file=create_tmp_file(commented_config)
58 | issues=[]
59 | val =get_value("GRUB_DEFAULT=",issues,tmp_file)
60 | assert val is None
61 | assert issues==[f"GRUB_DEFAULT= is commented out in {tmp_file}"]
62 |
63 | #check that the file doesn't change when trying to remove an already commented
64 | #config
65 | old_sum=get_file_sum(tmp_file)
66 | remove_value("GRUB_DEFAULT=",tmp_file)
67 | new_sum=get_file_sum(tmp_file)
68 | assert old_sum == new_sum
69 |
70 |
71 | set_value("GRUB_DEFAULT=","Garuda Linux",tmp_file)
72 |
73 |
74 | issues=[]
75 | print(tmp_file)
76 | assert get_value("GRUB_DEFAULT=",issues,tmp_file)=='Garuda Linux'
77 |
78 | remove_value("GRUB_DEFAULT=",tmp_file)
79 | assert get_value("GRUB_DEFAULT=",issues,tmp_file) is None
80 |
81 |
82 |
83 |
84 |
85 |
86 | config_last="""
87 | GRUB_DEFAULT="Manjaro Linux"
88 | GRUB_TIMEOUT=20
89 | GRUB_TIMEOUT_STYLE=menu"""
90 |
91 | def test_last_value(qtbot):
92 |
93 | tmp_file=f'{CACHE_LOC}/temp4.txt'
94 | subprocess.run([f'touch {tmp_file}'],shell=True)
95 |
96 | with open(tmp_file,'w') as f:
97 | f.write(config_last)
98 |
99 | issues=[]
100 | val =get_value("GRUB_TIMEOUT_STYLE=",issues,tmp_file)
101 | assert issues ==[]
102 | assert val=="menu"
103 |
104 | remove_value("GRUB_TIMEOUT_STYLE=",tmp_file)
105 | assert get_value("GRUB_TIMEOUT_STYLE=",issues,tmp_file) is None
106 |
107 |
108 |
--------------------------------------------------------------------------------
/tests/test_snapshots.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | from PyQt5 import QtWidgets,QtCore
4 | import subprocess
5 | from time import sleep
6 | from datetime import datetime as dt
7 | import re
8 | from tools import change_comboBox_current_index , windows
9 | PATH = os.path.dirname(os.path.realpath(__file__))
10 | #get the parent directory
11 | PATH = PATH[0:-5]
12 | # sys.path.append(PATH)
13 |
14 | from grubEditor import main
15 | from grubEditor.core import GRUB_CONF, conf_handler
16 |
17 | HOME=os.getenv("HOME")
18 |
19 | #checks if modified is shown but the main purpose of this function is to use it in other tests
20 | def test_change_config_modified(qtbot):
21 | #mw stand for mainWindow
22 | mw=main.Ui()
23 | main.MainWindow=mw;
24 | curr_ind = mw.comboBox_grub_default.currentIndex()
25 | for i in range(len(mw.all_entries)):
26 | if i!= curr_ind:
27 | grub_default_ind = i
28 | break
29 | # todo here
30 | mw.comboBox_grub_default.setCurrentIndex(grub_default_ind)
31 |
32 |
33 |
34 |
35 | def _test_ignore_changes(qtbot,mw,grub_default_ind):
36 | ''' Assumes that the grub default is in modified state '''
37 |
38 |
39 | #read data from /etc/default/grub
40 | with open('/etc/default/grub') as f:
41 | conf_data = f.read()
42 |
43 | assert mw.comboBox_grub_default in mw.original_modifiers
44 |
45 |
46 | qtbot.mouseClick(mw.create_snapshot_dialog.btn_ignore_changes, QtCore.Qt.LeftButton)
47 |
48 | assert not mw.create_snapshot_dialog.isVisible()
49 | date_time =str(dt.now()).replace(' ','_')[:-7]
50 |
51 | snapshots =[]
52 | for i in range(len(mw.configurations)):
53 | snapshots.append(mw.comboBox_configurations.itemText(i))
54 |
55 |
56 | #check if a snapshot was created
57 | for i in range(len(mw.configurations)):
58 | item_text=mw.comboBox_configurations.itemText(i)
59 | try:
60 | snapshots.remove(item_text)
61 | except:
62 | if '(modified)' not in item_text:
63 | assert item_text==date_time
64 | break
65 |
66 | #check if new snapshot actually ignored the changes
67 | with open(f'{main.DATA_LOC}/snapshots/{date_time}') as f:
68 | new_data =f.read()
69 |
70 | assert new_data ==conf_data
71 |
72 | assert grub_default_ind == mw.comboBox_grub_default.currentIndex()
73 |
74 | #check if it is showing a modified version of /etc/default/grub
75 |
76 | assert '(modified)' in mw.configurations[mw.comboBox_configurations.currentIndex()]
77 |
78 |
79 | def _test_add_changes_snapshot(qtbot,mw):
80 | #now lets do the same test but with btn_add_changes_to_snapshot
81 |
82 |
83 |
84 | date_time =str(dt.now()).replace(' ','_')[:-7]
85 |
86 | snapshots =[]
87 | for i in range(len(mw.configurations)):
88 | snapshots.append(mw.comboBox_configurations.itemText(i))
89 |
90 |
91 | #check if a snapshot was created
92 | for i in range(len(mw.configurations)):
93 | try:
94 | snapshots.remove(mw.comboBox_configurations.itemText(i))
95 | except Exception:
96 | if '(modified)' not in mw.comboBox_configurations.itemText(i):
97 | assert mw.comboBox_configurations.itemText(i)==date_time
98 | break
99 |
100 | #check if it is showing a modified version of /etc/default/grub
101 | assert '(modified)' in mw.configurations[mw.comboBox_configurations.currentIndex()]
102 |
103 | new_snapshot=f'{main.DATA_LOC}/snapshots/{date_time}'
104 |
105 | cmd = f"diff {new_snapshot} /etc/default/grub"
106 | diff_out = subprocess.run([cmd],shell=True,capture_output=True).stdout.decode()
107 | lines = diff_out.splitlines()
108 | print(lines,"lines")
109 | #first line would be something like 1c1 or 2c2
110 | assert lines[0][0]==lines[0][2]
111 | assert lines[0][1]=='c'
112 |
113 | assert 'GRUB_DEFAULT=' in lines[1]
114 | assert 'GRUB_DEFAULT=' in lines[3]
115 |
116 | def test_btn_create_snapshot(qtbot):
117 | #mw stand for mainWindow
118 | mw=main.Ui()
119 | main.MainWindow=mw;
120 | mw.tabWidget.setCurrentIndex(1)
121 | qtbot.addWidget(mw)
122 |
123 | #this test only works if grub default is predefined
124 | assert conf_handler.get(GRUB_CONF.GRUB_DEFAULT,[]) != "saved"
125 |
126 | #check if another configuration gets added when btn_create_snapshot is pressed
127 | snapshots_count =len(mw.configurations)-1
128 | qtbot.mouseClick(mw.btn_create_snapshot,QtCore.Qt.LeftButton)
129 | new_snapshots_count=len(mw.configurations)-1
130 | assert new_snapshots_count -1== snapshots_count
131 |
132 |
133 |
134 |
135 | #get to the edit_configurations tab and then change something to check if a new windows open ups to ask which
136 | # configuration i want to save to snapshot (from the file or edited one)
137 | mw.tabWidget.setCurrentIndex(0)
138 |
139 | grub_default_ind=change_comboBox_current_index(mw)
140 | assert '(modified)' in mw.configurations[mw.comboBox_configurations.currentIndex()]
141 |
142 | mw.tabWidget.setCurrentIndex(1)
143 |
144 | #remove default preference
145 | main.set_preference("create_snapshot","None")
146 |
147 |
148 | qtbot.mouseClick(mw.btn_create_snapshot,QtCore.Qt.LeftButton)
149 |
150 | assert mw.create_snapshot_dialog.isVisible()
151 |
152 | qtbot.mouseClick(mw.btn_create_snapshot,QtCore.Qt.LeftButton)
153 |
154 | assert mw.create_snapshot_dialog.isVisible()
155 |
156 | _test_ignore_changes(qtbot,mw,grub_default_ind)
157 |
158 | sleep(1)
159 |
160 |
161 | qtbot.mouseClick(mw.create_snapshot_dialog.btn_add_changes_to_snapshot, QtCore.Qt.LeftButton)
162 | assert not mw.create_snapshot_dialog.isVisible()
163 |
164 | _test_add_changes_snapshot(qtbot,mw)
165 |
166 | def test_btn_delete_snapshot(qtbot):
167 | #mw stand for mainWindow
168 | mw=main.Ui()
169 | main.MainWindow=mw;
170 | mw.tabWidget.setCurrentIndex(1)
171 |
172 | snapshots = []
173 | for i in range(len(mw.configurations)):
174 | snapshots.append(mw.comboBox_configurations.itemText(i))
175 |
176 | date_time =str(dt.now()).replace(' ','_')[:-7]
177 |
178 | qtbot.mouseClick(mw.btn_create_snapshot, QtCore.Qt.LeftButton)
179 |
180 | snapshot_is_in_list_var= False
181 | #check if a snapshot was created
182 | for i in range(len(mw.configurations)):
183 | try:
184 | snapshots.remove(mw.comboBox_configurations.itemText(i))
185 | except ValueError:
186 | if '(modified)' not in mw.comboBox_configurations.itemText(i):
187 | if mw.comboBox_configurations.itemText(i)==date_time:
188 | snapshot_is_in_list_var =True
189 | break
190 | assert snapshot_is_in_list_var==True
191 |
192 | for i in range(mw.VLayout_snapshot.count()):
193 | text =mw.VLayout_snapshot.itemAt(i).itemAt(0).widget().text()
194 | if text == date_time:
195 | new_snapshot_ind = i
196 | break
197 | assert main.GRUB_CONF_LOC== mw.configurations[mw.comboBox_configurations.currentIndex()]
198 |
199 | #now that we have found the index of the snapshot we have just created
200 | #Lets find the delete btn of the snapshot
201 | print(i)
202 | target_snapshot_row=mw.VLayout_snapshot.itemAt(new_snapshot_ind)
203 | btn_delete = target_snapshot_row.itemAt(3).widget()
204 |
205 | assert target_snapshot_row.itemAt(0).widget().text()==date_time
206 |
207 | mw.tabWidget.setCurrentIndex(0)
208 |
209 | #change something the edit_configurations UI and check if the value persists after deleti
210 | mw.btn_substract.click()
211 | assert '(modified)' in mw.configurations[mw.comboBox_configurations.currentIndex()]
212 |
213 | #get current value of grub timeout
214 | old_val = mw.ledit_grub_timeout.text()
215 |
216 | mw.tabWidget.setCurrentIndex(1)
217 |
218 | qtbot.mouseClick(btn_delete,QtCore.Qt.LeftButton)
219 | sleep(1)
220 | assert main.GRUB_CONF_LOC+"(modified)"== mw.configurations[mw.comboBox_configurations.currentIndex()]
221 |
222 | assert mw.VLayout_snapshot.itemAt(new_snapshot_ind) == None
223 |
224 | assert '(modified)' in mw.configurations[mw.comboBox_configurations.currentIndex()]
225 |
226 | mw.tabWidget.setCurrentIndex(0)
227 |
228 | #get the new value of grub timeout
229 | assert mw.ledit_grub_timeout.text() == old_val
230 |
231 | assert '(modified)' in mw.configurations[mw.comboBox_configurations.currentIndex()]
232 |
233 |
234 |
235 |
236 | #test the view snapshot button
237 | def test_btn_view(qtbot):
238 |
239 | #mw stand for mainWindow
240 | mw=main.Ui()
241 | main.MainWindow=mw
242 |
243 | #first find the view btn of the first snapshots
244 | #before that we need to create a snapshot if no snapshots exist
245 | if mw.VLayout_snapshot.itemAt(0) is None:
246 | qtbot.mouseClick(mw.btn_create_snapshot, QtCore.Qt.LeftButton)
247 |
248 | snapshot_name =mw.VLayout_snapshot.itemAt(0).layout().itemAt(0).widget().text()
249 | btn_view = mw.VLayout_snapshot.itemAt(0).layout().itemAt(2).widget()
250 |
251 | #check if it is view button
252 | assert btn_view.text()=='view'
253 |
254 | #delete the preferences file
255 | subprocess.run([f"rm {main.CONFIG_LOC}/main.json"],shell=True)
256 |
257 | qtbot.mouseClick(btn_view, QtCore.Qt.LeftButton)
258 |
259 |
260 | #check if btn_view_window is visible
261 | assert mw.view_btn_win.isVisible()
262 |
263 | mw.view_btn_win.close()
264 |
265 |
266 | #now check if that view_btn_win opens when preference has a value
267 | main.set_preference("view_default","default_text_editor")
268 |
269 | qtbot.mouseClick(btn_view,QtCore.Qt.LeftButton)
270 | assert not mw.view_btn_win.isVisible()
271 | assert mw.comboBox_configurations.currentText() =='/etc/default/grub'
272 |
273 | #! only works when kate is default text editor if not test has to be changed
274 | #might fail when kate takes too long to load
275 | sleep(5)
276 | windows= subprocess.check_output(["wmctrl -l "],shell=True).decode()
277 | assert f"{snapshot_name} — Kate" in windows
278 |
279 | main.set_preference("view_default","on_the_application_itself")
280 |
281 | qtbot.mouseClick(btn_view,QtCore.Qt.LeftButton)
282 | assert not mw.view_btn_win.isVisible()
283 | assert mw.tabWidget.currentIndex() ==0
284 |
285 | assert mw.comboBox_configurations.currentText() ==f"{snapshot_name}"
286 |
287 |
288 |
289 |
290 | def test_btn_set(qtbot):
291 | mw=main.Ui()
292 | main.MainWindow=mw
293 | mw.tabWidget.setCurrentIndex(1)
294 |
295 |
296 | if mw.VLayout_snapshot.count()==0:
297 | qtbot.mouseClick(mw.btn_create_snapshot,QtCore.Qt.LeftButton)
298 |
299 | row=mw.VLayout_snapshot.itemAt(0).layout()
300 | btn_set = row.itemAt(4).widget()
301 | snapshot_name=row.itemAt(0).widget().text()
302 | assert main.conf_handler.current_file==main.GRUB_CONF_LOC
303 | qtbot.mouseClick(btn_set,QtCore.Qt.LeftButton)
304 |
305 | assert mw.lbl_status.text() =="Waiting for authentication"
306 | sleep(1)
307 | win_list=windows()[0]
308 | auth_win_vis=False
309 | for i in range(len(win_list)):
310 | print(win_list[i])
311 | if "Authentication Required — PolicyKit1" in win_list[i]:
312 | auth_win_vis=True
313 | break
314 |
315 | assert auth_win_vis==True
316 |
317 | with qtbot.waitSignal(mw.set_snapshot_worker.signals.finished,timeout=30*1000):
318 | pass
319 |
320 | conf_sum = subprocess.check_output([f"sha256sum {main.GRUB_CONF_LOC}"],shell=True).decode()
321 | snapshot_sum = subprocess.check_output([f"sha256sum {main.DATA_LOC}/snapshots/{snapshot_name}"],shell=True).decode()
322 | assert conf_sum[:65]==snapshot_sum[:65]
--------------------------------------------------------------------------------
/tests/test_widget_dialog.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | from pathlib import Path
4 |
5 |
6 | PATH = os.path.dirname(os.path.realpath(__file__))
7 | #get the parent directory
8 | PATH = Path(PATH).parent
9 | sys.path.append(PATH)
10 |
11 |
12 | import main
13 |
14 | def test_setTextBtn(qtbot):
15 | mw=main.DialogUi()
16 | qtbot.addWidget(mw)
17 |
18 | mw.setText("hey there")
19 | assert mw.label.text()=="hey there"
20 |
21 | mw.setBtnOkText("Not ok")
22 | assert mw.btn_ok.text()=="Not ok"
23 |
24 |
--------------------------------------------------------------------------------
/tests/tools.py:
--------------------------------------------------------------------------------
1 | """ Tools for testing """
2 |
3 | import subprocess
4 | import traceback
5 | from random import randint
6 | import os
7 | import sys
8 |
9 | from pathlib import Path
10 |
11 |
12 |
13 | PATH=os.path.dirname(os.path.realpath(__file__))
14 | PARENT_PATH=str(Path(PATH).parent)
15 |
16 |
17 | print(PARENT_PATH)
18 | sys.path.append(PARENT_PATH)
19 | from grubEditor.main import main
20 |
21 | HOME=os.getenv("HOME")
22 |
23 | def change_comboBox_current_index(mw):
24 | """ Changes the current index of the combobox default entries
25 | for numbers from 0 - max it will put the minimum that is not current value
26 | """
27 | curr_ind = mw.comboBox_grub_default.currentIndex()
28 |
29 | for i in range(len(mw.all_entries)):
30 | if i!= curr_ind:
31 | grub_default_ind = i
32 | break
33 |
34 | mw.comboBox_grub_default.setCurrentIndex(grub_default_ind)
35 |
36 | return grub_default_ind
37 |
38 | def windows():
39 | """Returns windows names list and their id list in a tuple"""
40 | #shows all the visible windows
41 | exception_found=False
42 | while True:
43 | try:
44 | window_list= subprocess.check_output(['wmctrl -l'],shell=True)
45 | window_list= window_list.decode()
46 | # printer(type(window_list))
47 | window_list=window_list.split('\n')
48 | # printer(window_list,'window_list')
49 | exception_found=False
50 | except Exception as e:
51 | exception_found=True
52 | print(str(e))
53 | print(traceback.format_exc())
54 | print('error in windows function call but it was handled like a boss😎')
55 | print('hope i aint struck in this loop 😅')
56 | finally:
57 | if not exception_found:
58 | break
59 |
60 | final_id_list =[]
61 | final_window_list =[]
62 | for window in window_list:
63 | window = window.split()
64 | # print(window)
65 | while True:
66 | if '' in window:
67 | window.remove('')
68 | else:
69 | break
70 |
71 | # print(window,'window')
72 | if len(window)>3:
73 | # printer(window)
74 | final_id_list.append(window[0])
75 | window.pop(0)
76 | window.pop(0)
77 | window.pop(0)
78 |
79 | # printer(window)
80 | tmp=''
81 | # print(window)
82 | for word in window:
83 | tmp = tmp+' '+word
84 | # printer(tmp)
85 | final_window_list.append(tmp[1:])
86 | # print(final_window_list)
87 | return final_window_list,final_id_list
88 |
89 | def create_tmp_file(data):
90 | """ Creates a file with the data provided as argument and returns the path of file """
91 | value =randint(0,20)
92 | tmp_file=f'{HOME}/.cache/grub-editor/temp{value}.txt'
93 | subprocess.run([f'touch {tmp_file}'],shell=True)
94 |
95 | with open(tmp_file,'w') as f:
96 | f.write(data)
97 |
98 | return tmp_file
99 |
100 | def create_test_file(data):
101 | """ Creates a file with the data provided as argument and returns the name of file """
102 | value =randint(0,20)
103 | test_file=f'{HOME}/.cache/grub-editor/test{value}.txt'
104 | subprocess.run([f'touch {test_file}'],shell=True)
105 |
106 | with open(test_file,'w') as f:
107 | f.write(data)
108 |
109 | return test_file
110 |
111 | def create_snapshot(data):
112 | """ Create a snapshot with the data provided as argument
113 | and returns the name of the snapshot
114 | Eg name: test_snapshot0
115 | Snapshot path is f"{main.DATA_LOC}/snapshots/"
116 |
117 | """
118 | num=randint(0,20)
119 |
120 | snapshot_name=f"{main.DATA_LOC}/snapshots/test_snapshot{num}"
121 | subprocess.run([f"touch {snapshot_name}"],shell=True)
122 |
123 | with open(snapshot_name,'w') as f:
124 | f.write(data)
125 |
126 | return f"test_snapshot{num}"
127 |
128 | def scrollArea_visible(mw,targetLayout=None)->bool:
129 | """ Checks if the scroll area which shows more details is visible
130 | Warning:dependant on the current layout of window. May fail if a new widget was inserted
131 | """
132 | if targetLayout is None:
133 | targetLayout=mw.verticalLayout_2
134 |
135 | print(targetLayout.count())
136 | for i in range(targetLayout.count()):
137 | print(i,targetLayout.itemAt(i).widget())
138 | if 'QScrollArea' in str(targetLayout.itemAt(i).widget()):
139 | return True
140 | return False
141 |
142 |
143 | def password_not_entered(mw):
144 | return mw.lbl_status.text() =="Waiting for authentication"
145 |
146 | def delete_pref():
147 | """ Deletes the user preference file """
148 | subprocess.run([f"rm {main.CONFIG_LOC}/main.json"],shell=True)
149 |
150 | def get_file_sum(file_path):
151 | """ Returns the sum of the file provided as argument """
152 | return subprocess.check_output([f"sha256sum {file_path}"],shell=True).decode()
153 |
--------------------------------------------------------------------------------
/todo.txt:
--------------------------------------------------------------------------------
1 |
2 | check if the current grub.cfg is up to date with /etc/default/grub
3 | check if other operating systems are there by executing os prober and if it takes too long then use grub.cfg to find out
4 | show no snapshots found above the list widget if no snapshots were found
5 | add UEFI support
6 |
7 | if grub default has an invalid value then a Dialog is show but add a checkbox to allow the user to disable that dialog
8 |
9 | write tests for checks if error_dialog text of the label part in qt designer is in the value used in error_dialog.py
10 |
11 | dynamicaly set combobox item text with resize event so that no part if item text will be hidden
12 |
13 |
14 | fix bug that causes the loading configuration from to change when default text editor is selected on view button 's new window
15 |
16 |
17 | pop the last invalid default entry when fix has finished
18 |
19 | checkbox do this everytime in invalid grub default fixer
20 |
21 | if user preferes fixing the invalid_kernel_version then inform the user that permissions are needed fix the invalid kernerl
22 | if /etc/default/grub has invallid entry
23 |
24 | do not allow brackets in the value of snapshot names
25 |
26 |
27 | Invalid entry grub default error when look for other os is turned off
28 |
29 | Fix inconsistency of removing quotes in GRUB_DEFAULT with get_value but for other keys accepting argument called remove_quotes_
30 |
31 | Add tests to handle GRUB_DEFAULT covered in single quotes
--------------------------------------------------------------------------------