Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/qt6 #490

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ insert_final_newline = true
[*.{js,py}]
charset = utf-8

# 4 space indentation
[*.py]
indent_style = tab
indent_size = 4
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,25 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, macos-11]
python-version: [3.10.0]
os: [ubuntu-latest, macos-latest]
python-version: [3.12.2]
# include:
# - os: windows-latest
# python-version: 3.9.7
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Cache Qt
id: cache-qt
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ../Qt
key: ${{ runner.os }}-QtCache
- name: Install Qt
uses: jurplel/install-qt-action@v2
uses: jurplel/install-qt-action@v3
with:
cached: ${{ steps.cache-qt.outputs.cache-hit }}
- name: Install Linux dependencies
Expand Down
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=PyQt5
extension-pkg-whitelist=PyQt6

# Specify a score threshold to be exceeded before program exits with error.
fail-under=90
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ NUT has the ability to interact with Google Drive. For this to work, you will ne
<details>
<summary>Details</summary>

* Install Python 3.9+ from your preferred package manager, along with the `libusb`, `python3-pip` & `python3-pyqt5` packages
* Install Python 3.9+ from your preferred package manager, along with the `libusb`, `python3-pip` & `python3-pyqt6` packages
* Install `curl` with the openssl backend - install `libssl-dev` (ie, `apt install libssl-dev libcurl4-openssl-dev`)
* Clone this repository to desired directory and change your working directory to the cloned repository
* Install the PIP modules with the following command `pip3 install -r requirements.txt`. If you previously tried installing pycurl and get the error `libcurl link-time ssl backend (openssl) is different from compile-time ssl backend (none/other)`, uninstall it, make sure to follow step 2 again (installing curl with the openssl backend), and `pip3 install pycurl --no-cache-dir`
Expand All @@ -77,12 +77,12 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="27e2", GROUP="plug
<details>
<summary>Details</summary>

* Install Python 3.9 and PyQt5 via Homebrew (`brew install [email protected] pyqt@5`)
* Install Python 3.9 and PyQt6 via Homebrew (`brew install [email protected] pyqt@6`)
* Install pyenv: [pyenv](https://github.com/pyenv/pyenv) + [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv) (`brew install pyenv pyenv-virtualenv` and follow install directions)
* Install `libusb` (`brew install libusb`)
* Install `curl` with the openssl backend (`brew uninstall --ignore-dependencies curl && brew install curl`)
* Install Python 3.9.7 with pyenv and set it as the default (`pyenv install 3.9.7 && pyenv global 3.9.7`)
* Load the system python's site-packages via pyenv's python. This is required to use PyQT5 from Homebrew.
* Load the system python's site-packages via pyenv's python. This is required to use PyQT6 from Homebrew.
- Get the Homebrew Python site-packages path (via `brew info [email protected]`).
- Add it to the load path of your pyenv's Python install (cd `pyenv root`).
- To do this, go to the site packages directory of your pyenv install (ie. `$HOME/.pyenv/versions/3.9.7/lib/python3.9/site-packages`) and create an file named `homebrew.pth` containing the path for Homebrew Python's site packages directory (ie. `/opt/homebrew/lib/python3.9/site-packages`)
Expand Down
10 changes: 5 additions & 5 deletions autoformat
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# TODO: migrate to tasks.py (invoke autoformat)

# delete python cache
find . -type f -name "*.py[co]" -delete
find . -type d -name "__pycache__" -delete
find ./ -type f -name "*.py[co]" -delete
find ./ -type d -name "__pycache__" -delete
# execute autoformatters
find . -iname '*.py' -exec autopep8 --global-config setup.cfg --aggressive -i "{}" \;
find . -iname '*.py' -exec autoflake --in-place --remove-all-unused-imports --remove-unused-variables "{}" \;
find . -iname '*.py' -exec sed -i 's/ /\t/g' "{}" \;
find ./ -iname '*.py' -exec autopep8 --global-config setup.cfg --aggressive -i "{}" \;
find ./ -iname '*.py' -exec autoflake --in-place --remove-all-unused-imports --remove-unused-variables "{}" \;
find ./ -iname '*.py' -exec sed -i 's/ /\t/g' "{}" \;
89 changes: 55 additions & 34 deletions gui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import os
import webbrowser

from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import (QWidget, QDesktopWidget, QVBoxLayout, QMessageBox)
from PyQt6.QtGui import QGuiApplication
from PyQt6.QtCore import pyqtSlot
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QMessageBox

import gui.tabs
import gui.panes.files
Expand All @@ -20,10 +21,11 @@
import Fs.driver.init
from translator import tr


class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'NUT 3.3'
self.title = "NUT 3.3"
self.needsRefresh = False
self.isInitialized = False
self.initUI()
Expand All @@ -34,7 +36,7 @@ def refresh(self):
def initUI(self):
self.isInitialized = False
self.setWindowTitle(self.title)
screen = QDesktopWidget().screenGeometry()
screen = QGuiApplication.primaryScreen().geometry()
left = int(screen.width() / 4)
top = int(screen.height() / 4)
width = int(screen.width() / 2)
Expand All @@ -47,15 +49,29 @@ def initUI(self):
layout.addLayout(self.header.layout)
self.files = gui.panes.files.Files()

self.tabs = gui.tabs.Tabs({
tr('main.grid.files'): self.files,
tr('main.grid.filters'): gui.panes.filters.Filters(),
tr('main.grid.save_paths'): gui.panes.format.Format(),
tr('main.grid.local_scan_paths'): gui.panes.dirlist.DirList(Config.paths.scan, self.saveScanPaths, rowType=gui.panes.dirlist.DirectoryLocal),
tr('main.grid.remote_pull_paths'): gui.panes.dirlist.DirList(Config.pullUrls, self.savePullUrls, rowType=gui.panes.dirlist.DirectoryNetwork),
tr('main.grid.users'): gui.panes.dirlist.DirList(list(Users.users.values()), self.saveUsers, rowType=gui.panes.dirlist.User), # rowType
tr('main.grid.options'): gui.panes.options.Options()
})
self.tabs = gui.tabs.Tabs(
{
tr("main.grid.files"): self.files,
tr("main.grid.filters"): gui.panes.filters.Filters(),
tr("main.grid.save_paths"): gui.panes.format.Format(),
tr("main.grid.local_scan_paths"): gui.panes.dirlist.DirList(
Config.paths.scan,
self.saveScanPaths,
rowType=gui.panes.dirlist.DirectoryLocal,
),
tr("main.grid.remote_pull_paths"): gui.panes.dirlist.DirList(
Config.pullUrls,
self.savePullUrls,
rowType=gui.panes.dirlist.DirectoryNetwork,
),
tr("main.grid.users"): gui.panes.dirlist.DirList(
list(Users.users.values()),
self.saveUsers,
rowType=gui.panes.dirlist.User,
), # rowType
tr("main.grid.options"): gui.panes.options.Options(),
}
)
layout.addWidget(self.tabs)

self.progress = Progress(self)
Expand Down Expand Up @@ -120,9 +136,12 @@ def on_compress():
nut.compressAll(Config.compression.level)

def on_organize(self):
answer = QMessageBox.question(self, tr('main.top_menu.organize'),
tr('main.dialog.organize_confirmation'),
QMessageBox.Yes | QMessageBox.No)
answer = QMessageBox.question(
self,
tr("main.top_menu.organize"),
tr("main.dialog.organize_confirmation"),
QMessageBox.Yes | QMessageBox.No,
)
if answer == QMessageBox.Yes:
nut.organize()

Expand All @@ -145,49 +164,51 @@ def on_titledb():
def on_gdrive(self):
if Config.getGdriveCredentialsFile() is None:
webbrowser.open_new_tab(
'https://developers.google.com/drive/api/v3/quickstart/go',
"https://developers.google.com/drive/api/v3/quickstart/go",
)
QMessageBox.information(
self,
'Google Drive OAuth Setup',
"You require a credentials.json file to set up Google Drive " +
"OAuth. This file can be obtained from " +
"https://developers.google.com/drive/api/v3/quickstart/go , " +
"click on the blue button that says 'Enable the Drive API' " +
"and save the credentials.json to t his application's " +
"directory.",
"Google Drive OAuth Setup",
"You require a credentials.json file to set up Google Drive "
+ "OAuth. This file can be obtained from "
+ "https://developers.google.com/drive/api/v3/quickstart/go , "
+ "click on the blue button that says 'Enable the Drive API' "
+ "and save the credentials.json to t his application's "
+ "directory.",
)
else:
buttonReply = QMessageBox.question(
self,
'Google Drive OAuth Setup',
"Google Drive OAuth Setup",
"Do you you want to setup GDrive OAuth?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No,
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)

if buttonReply == QMessageBox.Yes:
try:
os.unlink('gdrive.token')
os.unlink("gdrive.token")
except OSError:
pass

try:
os.unlink('token.pickle')
os.unlink("token.pickle")
except OSError:
pass

Fs.driver.gdrive.getGdriveToken(None, None)
QMessageBox.information(
self,
'Google Drive OAuth Setup',
"OAuth has completed. Please copy gdrive.token and " +
"credentials.json to your Nintendo Switch's " +
"sdmc:/switch/tinfoil/ and/or sdmc:/switch/sx/ " +
"directories."
"Google Drive OAuth Setup",
"OAuth has completed. Please copy gdrive.token and "
+ "credentials.json to your Nintendo Switch's "
+ "sdmc:/switch/tinfoil/ and/or sdmc:/switch/sx/ "
+ "directories.",
)

@staticmethod
def closeEvent(event):
del event
# pylint: disable=fixme
# TODO: implement a graceful shutdown of other threads
os._exit(0) # pylint: disable=protected-access
os._exit(0) # pylint: disable=protected-access
41 changes: 25 additions & 16 deletions gui/header.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
import socket

from PyQt5.QtCore import (Qt, QTimer)
from PyQt5.QtWidgets import (QVBoxLayout, QHBoxLayout, QPushButton, QLabel)
from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QLabel

from nut import Config, Users, Usb
from translator import tr


def _get_ip_address():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
Expand All @@ -17,50 +18,58 @@ def _get_ip_address():
except OSError:
return None


def _create_button(app, parent, text, max_width, handler):
widget = QPushButton(text, app)
widget.setMaximumWidth(max_width)
widget.clicked.connect(handler)
widget.setFocusPolicy(Qt.StrongFocus)
widget.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
parent.addWidget(widget)
return widget

class Header: # pylint: disable=too-many-instance-attributes,too-few-public-methods

class Header: # pylint: disable=too-many-instance-attributes,too-few-public-methods
def __init__(self, app):
self.layout = QVBoxLayout()

top = QHBoxLayout()
bottom = QHBoxLayout()

self.scan = _create_button(app, top, tr('main.top_menu.scan'), 100, app.on_scan)
_create_button(app, top, tr('main.top_menu.organize'), 200, app.on_organize)
self.pull = _create_button(app, top, tr('main.top_menu.pull'), 100, app.on_pull)
self.titledb = _create_button(app, top, tr('main.top_menu.update_titledb'), 200, app.on_titledb)
_create_button(app, top, tr('main.top_menu.decompress_nsz'), 200, app.on_decompress)
_create_button(app, top, tr('main.top_menu.compress_nsp'), 200, app.on_compress)
self.gdrive = _create_button(app, top, tr('main.top_menu.setup_gdrive'), 200, app.on_gdrive)
self.scan = _create_button(app, top, tr("main.top_menu.scan"), 100, app.on_scan)
_create_button(app, top, tr("main.top_menu.organize"), 200, app.on_organize)
self.pull = _create_button(app, top, tr("main.top_menu.pull"), 100, app.on_pull)
self.titledb = _create_button(
app, top, tr("main.top_menu.update_titledb"), 200, app.on_titledb
)
_create_button(
app, top, tr("main.top_menu.decompress_nsz"), 200, app.on_decompress
)
_create_button(app, top, tr("main.top_menu.compress_nsp"), 200, app.on_compress)
self.gdrive = _create_button(
app, top, tr("main.top_menu.setup_gdrive"), 200, app.on_gdrive
)

top.addStretch()

ipAddr = _get_ip_address()

if ipAddr:
self.serverInfo = QLabel(
f"<b>{tr('main.status.ip')}:</b> {ipAddr} <b>{tr('main.status.port')}:</b> {Config.server.port} " +
f"<b>{tr('main.status.user')}:</b> {Users.first().id} <b>{tr('main.status.password')}:</b> " +
f"{Users.first().password}"
f"<b>{tr('main.status.ip')}:</b> {ipAddr} <b>{tr('main.status.port')}:</b> {Config.server.port} "
+ f"<b>{tr('main.status.user')}:</b> {Users.first().id} <b>{tr('main.status.password')}:</b> "
+ f"{Users.first().password}"
)
else:
self.serverInfo = QLabel("<b>Offline</b>")

self.serverInfo.setMinimumWidth(200)
self.serverInfo.setAlignment(Qt.AlignCenter)
self.serverInfo.setAlignment(Qt.AlignmentFlag.AlignCenter)
bottom.addWidget(self.serverInfo)
bottom.addStretch()

self.usbStatus = QLabel("<b>USB:</b> " + tr("usb.status." + Usb.status))
self.usbStatus.setMinimumWidth(50)
self.usbStatus.setAlignment(Qt.AlignCenter)
self.usbStatus.setAlignment(Qt.AlignmentFlag.AlignCenter)
bottom.addWidget(self.usbStatus)

self.timer = QTimer()
Expand Down
Loading
Loading