Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
I
ingame
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vladislav
ingame
Commits
e96ebca9
Commit
e96ebca9
authored
Jun 28, 2024
by
Yankovskiy Georgiy
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Commits
918563db
,
a19804a8
refactor (part 1 of 2)
parent
e9d90e00
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
207 additions
and
126 deletions
+207
-126
App.py
ingame/models/App.py
+46
-52
GameAgent.py
ingame/models/GameAgent.py
+91
-59
GameDescription.py
ingame/models/GameDescription.py
+19
-0
GameEntry.py
ingame/models/GameEntry.py
+10
-0
GamesModel.py
ingame/models/GamesModel.py
+9
-14
poetry.lock
poetry.lock
+30
-1
pyproject.toml
pyproject.toml
+2
-0
No files found.
ingame/models/App.py
View file @
e96ebca9
...
...
@@ -9,9 +9,11 @@ from typing import AnyStr, Union
from
PySide6
import
QtCore
from
os.path
import
expanduser
from
desktop_parser
import
DesktopFile
from
platformdirs
import
user_cache_dir
,
user_config_dir
from
ingame.models.Gamepad
import
Gamepad
from
ingame.models.GamesModel
import
Game
,
GamesModel
from
ingame.models.GamesModel
import
GamesModel
from
ingame.models.GameEntry
import
GameEntry
from
ingame.models.GameAgent
import
GameAgent
from
PySide6.QtCore
import
Property
,
Signal
,
Slot
,
QObject
,
Qt
...
...
@@ -24,11 +26,12 @@ class GameShortcut:
class
App
(
QtCore
.
QObject
):
app_name
=
"ingame"
app_author
=
"foss"
game_started
=
Signal
(
bool
,
name
=
"gameStarted"
)
game_ended
=
Signal
(
bool
,
name
=
"gameEnded"
)
data_found
=
Signal
(
dict
,
name
=
"gotGameData"
)
gamepad_clicked_LB
=
Signal
(
bool
,
name
=
"gamepadClickedLB"
)
gamepad_clicked_RB
=
Signal
(
bool
,
name
=
"gamepadClickedRB"
)
gamepad_clicked_apply
=
Signal
(
bool
,
name
=
"gamepadClickedApply"
)
...
...
@@ -38,9 +41,12 @@ class App(QtCore.QObject):
def
__init__
(
self
):
super
()
.
__init__
()
self
.
games_model
:
GamesModel
=
GamesModel
()
self
.
home
:
AnyStr
=
expanduser
(
'~'
)
self
.
config_location
:
str
=
'/.config/PortProton.conf'
self
.
config_path
=
user_config_dir
(
App
.
app_name
,
App
.
app_author
)
self
.
cache_path
=
user_cache_dir
(
App
.
app_name
,
App
.
app_author
)
self
.
games_model
:
GamesModel
=
GamesModel
()
self
.
portproton_config_location
:
str
=
'/.config/PortProton.conf'
self
.
portproton_location
:
str
=
''
self
.
running_game_process
:
Union
[
subprocess
.
Popen
,
None
]
=
None
...
...
@@ -52,13 +58,13 @@ class App(QtCore.QObject):
self
.
gamepad
.
r_clicked
=
lambda
:
self
.
gamepad_axis_right
.
emit
(
True
)
self
.
gamepad
.
back_clicked
=
lambda
:
self
.
gamepad_clicked_back
.
emit
(
True
)
self
.
agent
=
GameAgent
()
self
.
agent
=
GameAgent
(
self
.
config_path
,
self
.
cache_path
)
self
.
setup
()
def
setup
(
self
):
try
:
with
open
(
self
.
home
+
self
.
config_location
,
'r'
)
as
file
:
with
open
(
self
.
home
+
self
.
portproton_
config_location
,
'r'
)
as
file
:
self
.
portproton_location
=
file
.
read
()
.
strip
()
print
(
f
'Current PortProton location: {self.portproton_location}'
)
...
...
@@ -67,79 +73,73 @@ class App(QtCore.QObject):
for
val
in
files
:
desktop_file
=
DesktopFile
.
from_file
(
val
)
data
=
desktop_file
.
data
entry
=
data
[
'Desktop Entry'
]
d
esktop_file_d
ata
=
desktop_file
.
data
desktop_entry
=
desktop_file_
data
[
'Desktop Entry'
]
_name
=
entry
[
'Name'
]
or
'generic'
_exec
=
'Exec'
in
entry
and
entry
[
'Exec'
]
or
''
_icon
=
entry
[
'Icon'
]
entry_name
=
desktop_
entry
[
'Name'
]
or
'generic'
entry_exec
=
'Exec'
in
desktop_entry
and
desktop_
entry
[
'Exec'
]
or
''
entry_icon
=
desktop_
entry
[
'Icon'
]
assert
(
isinstance
(
_name
,
str
)
and
isinstance
(
_exec
,
str
)
and
isinstance
(
_icon
,
str
))
assert
(
isinstance
(
entry
_name
,
str
)
and
isinstance
(
entry
_exec
,
str
)
and
isinstance
(
entry
_icon
,
str
))
e
xec_split
=
_exec
.
split
(
' '
)
e
ntry_exec_split
=
entry
_exec
.
split
(
' '
)
# Ignore extra non-related desktop entries
if
(
len
(
exec_split
)
<=
1
or
(
'data/scripts/start.sh'
not
in
e
xec_split
[
1
]
or
'
%
F'
in
exec_split
[
-
1
])):
if
(
len
(
e
ntry_e
xec_split
)
<=
1
or
(
'data/scripts/start.sh'
not
in
e
ntry_exec_split
[
1
]
or
'
%
F'
in
entry_
exec_split
[
-
1
])):
continue
# TODO parse product name
_icon
=
(
os
.
path
.
isfile
(
_icon
)
and
_icon
or
os
.
path
.
realpath
(
f
"{Path(__file__).resolve().parent}../../../qml/images/PUBG.png"
))
# Автозапуск игры:
# PW_GUI_DISABLED_CS=1
# START_FROM_STEAM=1
entry_icon
=
(
os
.
path
.
isfile
(
entry_icon
)
and
entry_icon
)
or
''
# Remove extra env in the beginning
_exec
=
_exec
[
4
:
len
(
_exec
)]
_exec
=
f
"env START_FROM_STEAM=1 {_exec}"
entry_exec
=
f
"env START_FROM_STEAM=1 {entry_exec[4:len(entry_exec)]}"
self
.
games_model
.
add_game
(
Game
(
name
=
_name
,
icon
=
_icon
,
exec
=
_exec
))
self
.
games_model
.
add_game
(
Game
Entry
(
name
=
entry_name
,
icon
=
entry_icon
,
exec
=
entry
_exec
))
self
.
gamepad
.
run
()
self
.
retrieve_games_details
()
except
FileNotFoundError
:
print
(
'File not found'
)
except
Exception
as
e
:
print
(
'An error occurred'
,
e
)
pass
### CALLBACKS ###
# TODO: refactor!
def
retrieve_games_details
(
self
):
def
retrieve_games_details_thread
(
t
):
game_entry
:
GameEntry
for
game_entry
in
self
.
games_model
.
games_list
:
game_description
=
t
.
agent
.
retrieve_game_description
(
game_entry
.
name
)
game_entry
.
icon
=
game_description
[
'image_location_path'
]
or
game_entry
.
icon
thread
=
threading
.
Thread
(
target
=
retrieve_games_details_thread
,
args
=
(
self
,))
thread
.
start
()
''' CALLBACKS '''
def
close_event
(
self
):
# do stuff
# if can_exit:
self
.
gamepad
.
terminate
()
# event.accept() # let the window close
# else:
# event.ignore()
self
.
agent
.
clean_data
()
# self.agent.save_db()
### SLOTS ###
''' SLOTS '''
@Slot
(
str
,
result
=
dict
)
def
get_game_data
(
self
,
game_name
):
#print(game_name)
def
search_thread
(
t
,
name
):
search_result
=
t
.
agent
.
search_game
(
name
)
search_result
=
t
.
agent
.
retrieve_game_description
(
name
)
t
.
data_found
.
emit
(
search_result
)
return
thread
=
threading
.
Thread
(
target
=
search_thread
,
args
=
(
self
,
game_name
))
thread
.
start
()
pass
@Slot
(
str
)
def
start_game
(
self
,
exec
):
def
start_game
(
self
,
_
exec
):
self
.
game_started
.
emit
(
True
)
def
run_in_thread
(
t
,
_exec
):
...
...
@@ -152,18 +152,12 @@ class App(QtCore.QObject):
)
t
.
running_game_process
.
wait
()
t
.
game_ended
.
emit
(
True
)
# output = self.running_game_process.stdout.read()
# self.running_game_process.stdout.close()
return
thread
=
threading
.
Thread
(
target
=
run_in_thread
,
args
=
(
self
,
exec
))
thread
=
threading
.
Thread
(
target
=
run_in_thread
,
args
=
(
self
,
_
exec
))
thread
.
start
()
pass
### PROPERTIES ###
''' PROPERTIES '''
@Property
(
QObject
,
constant
=
True
)
def
games
(
self
):
...
...
ingame/models/GameAgent.py
View file @
e96ebca9
import
os
import
pickle
import
requests
from
steam_web_api
import
Steam
# TODO:
# [?] Определиться, используется ли Lutris. Если да, вместо этого будет обращение к нему. Если нет,
# продумать "рыбу" более логично.
# [?] Починить отображение системных требований (точнее, разобраться, что именно возвращает API.
# [done 1/2] Додумать форматированные данные, что именно мы видим на странице игры?
from
steamgrid
import
SteamGridDB
from
ingame.models.GameDescription
import
GameDescription
class
GameAgent
:
generic_name
=
"Risk of rain 2"
datapath
=
".agent-data"
all_data
=
dict
()
scenario
=
0
#
generic_name = "Risk of rain 2"
# scenario = 0
# db_storage_path = ".agent-data"
# data = dict()
def
__init__
(
self
):
def
__init__
(
self
,
config_path
,
cache_path
):
super
()
.
__init__
()
agent_key
=
"SOME_KEY_HERE_I_GUESS"
self
.
steam_process
=
Steam
(
agent_key
)
self
.
get_all_data
()
# TODO: move API tokens to GUI settings tab / environmental variables
self
.
steam_grid_db_client
=
SteamGridDB
(
'66827eabea66de47d036777ed2be87b2'
)
self
.
steam_client
=
Steam
(
"SOME_KEY_HERE_I_GUESS"
)
self
.
config_path
=
config_path
self
.
cache_path
=
cache_path
self
.
db_storage_path
=
config_path
+
"/.agent-data"
self
.
steam_grid_db_images_path
=
cache_path
+
"/steam_grid_db_images"
self
.
data
=
dict
()
os
.
makedirs
(
self
.
config_path
,
exist_ok
=
True
,
mode
=
0
o755
)
os
.
makedirs
(
self
.
steam_grid_db_images_path
,
exist_ok
=
True
,
mode
=
0
o755
)
self
.
load_db
()
''' USAGE '''
def
retrieve_game_description
(
self
,
game_name
):
if
game_name
not
in
self
.
data
:
# TODO: checkup for failed requests
search_results
=
self
.
steam_client
.
apps
.
search_games
(
game_name
)
self
.
add_game_description
(
search_results
,
game_name
)
game_description
=
self
.
data
[
game_name
]
return
game_description
.
as_dict
()
def
steam_grid_db_retrieve_image
(
self
,
game_name
):
try
:
save_path
=
f
"{self.steam_grid_db_images_path}/{game_name}.png"
if
os
.
path
.
exists
(
save_path
):
return
save_path
# TODO: checkup for failed requests
result
=
self
.
steam_grid_db_client
.
search_game
(
game_name
)
grids
=
self
.
steam_grid_db_client
.
get_grids_by_gameid
(
list
([
result
[
0
]
.
id
]))
# TODO: too slow, replace loop o(n) with o(1) if possible
for
grid
in
grids
:
if
grid
.
height
==
900
and
grid
.
width
==
600
:
url_img
=
grid
.
url
response
=
requests
.
get
(
url_img
)
with
open
(
save_path
,
'wb'
)
as
file
:
file
.
write
(
response
.
content
)
# return url_img
return
save_path
return
''
except
:
return
''
''' DATABASE '''
def
add_game_description
(
self
,
search_results
,
game_name
):
game_description
=
GameDescription
()
# Steam game info
if
search_results
[
'apps'
]:
game_id
=
search_results
[
'apps'
][
0
][
'id'
][
0
]
# TODO: checkup for failed requests
app_details
=
self
.
steam_client
.
apps
.
get_app_details
(
game_id
)
app_data
=
app_details
[
str
(
game_id
)][
'data'
]
def
add_game_info
(
self
,
data
,
name
):
if
not
data
[
'apps'
]:
self
.
all_data
[
name
]
=
0
else
:
game_id
=
data
[
'apps'
][
0
][
'id'
]
data
=
self
.
steam_process
.
apps
.
get_app_details
(
game_id
)
self
.
all_data
[
name
]
=
data
[
str
(
game_id
[
0
])][
'data'
]
with
open
(
self
.
datapath
,
"wb+"
)
as
datafile
:
pickle
.
dump
(
self
.
all_data
,
datafile
)
self
.
get_all_data
()
game_description
.
title
=
app_data
[
'name'
]
game_description
.
desc
=
app_data
[
'short_description'
]
game_description
.
reqs
=
((
app_data
[
'linux_requirements'
]
and
(
app_data
[
'linux_requirements'
][
'minimum'
]
or
app_data
[
'linux_requirements'
][
'recommended'
]
))
or
(
app_data
[
'pc_requirements'
]
and
(
app_data
[
'pc_requirements'
][
'minimum'
]
or
app_data
[
'pc_requirements'
][
'recommended'
]
))
or
'-'
)
game_description
.
languages
=
app_data
[
'supported_languages'
]
def
search_game
(
self
,
game_name
):
# Steam Grid DB image retrieving
game_description
.
image_location_path
=
self
.
steam_grid_db_retrieve_image
(
game_name
)
self
.
get_all_data
()
self
.
data
[
game_name
]
=
game_description
self
.
save_db
()
if
game_name
in
self
.
all_data
:
print
(
"ITS HERE!"
)
else
:
search_results
=
self
.
steam_process
.
apps
.
search_games
(
game_name
)
self
.
add_game_info
(
search_results
,
game_name
)
return
self
.
format_game_data
(
self
.
all_data
[
game_name
])
def
save_db
(
self
):
with
open
(
self
.
db_storage_path
,
"wb+"
)
as
datafile
:
pickle
.
dump
(
self
.
data
,
datafile
)
def
get_all_data
(
self
):
def
load_db
(
self
):
try
:
with
open
(
self
.
d
ata
path
,
"rb"
)
as
datafile
:
self
.
all_
data
=
pickle
.
load
(
datafile
)
with
open
(
self
.
d
b_storage_
path
,
"rb"
)
as
datafile
:
self
.
data
=
pickle
.
load
(
datafile
)
except
FileNotFoundError
:
self
.
all_data
=
dict
()
def
format_game_data
(
self
,
game_data
):
formatted_data
=
dict
()
if
game_data
!=
0
:
formatted_data
[
'title'
]
=
game_data
[
'name'
]
formatted_data
[
'desc'
]
=
game_data
[
'short_description'
]
formatted_data
[
'languages'
]
=
game_data
[
'supported_languages'
]
formatted_data
[
'reqs'
]
=
game_data
[
'linux_requirements'
]
# for key, value in formatted_data.items():
# print("{0}: {1}".format(key, value))
else
:
#TODO исправить это недоразумение, временная затычка
formatted_data
[
'title'
]
=
"Информация не найдена!"
formatted_data
[
'desc'
]
=
"Информация не найдена!"
formatted_data
[
'languages'
]
=
"Информация не найдена!"
formatted_data
[
'reqs'
]
=
"Информация не найдена!"
# print(formatted_data)
return
formatted_data
self
.
data
=
dict
()
def
clean_data
(
self
):
self
.
all_data
=
dict
()
with
open
(
self
.
datapath
,
"wb"
)
as
datafile
:
pickle
.
dump
(
self
.
all_data
,
datafile
)
print
(
"data cleaned"
)
self
.
data
=
dict
()
ingame/models/GameDescription.py
0 → 100644
View file @
e96ebca9
from
dataclasses
import
dataclass
@dataclass
class
GameDescription
:
title
:
str
=
'Информация не найдена!'
desc
:
str
=
'Информация не найдена!'
languages
:
str
=
'Информация не найдена!'
reqs
:
str
=
'Информация не найдена!'
image_location_path
:
str
=
''
def
as_dict
(
self
):
formatted_data
=
dict
()
formatted_data
[
'title'
]
=
self
.
title
formatted_data
[
'desc'
]
=
self
.
desc
formatted_data
[
'languages'
]
=
self
.
languages
formatted_data
[
'reqs'
]
=
self
.
reqs
formatted_data
[
'image_location_path'
]
=
self
.
image_location_path
return
formatted_data
ingame/models/GameEntry.py
0 → 100644
View file @
e96ebca9
from
dataclasses
import
dataclass
@dataclass
class
GameEntry
:
name
:
str
=
''
exec
:
str
=
''
icon
:
str
=
''
ingame/models/GamesModel.py
View file @
e96ebca9
import
typing
from
dataclasses
import
dataclass
,
fields
from
dataclasses
import
fields
from
PySide6.QtCore
import
QAbstractListModel
,
QModelIndex
,
Qt
,
QByteArray
@dataclass
class
Game
:
name
:
str
=
''
exec
:
str
=
''
icon
:
str
=
''
from
ingame.models.GameEntry
import
GameEntry
class
GamesModel
(
QAbstractListModel
):
def
__init__
(
self
):
super
()
.
__init__
()
self
.
_list
=
[]
self
.
games
_list
=
[]
def
data
(
self
,
index
:
QModelIndex
,
role
:
int
=
Qt
.
DisplayRole
)
->
typing
.
Any
:
if
0
<=
index
.
row
()
<
self
.
rowCount
():
student
=
self
.
_list
[
index
.
row
()]
student
=
self
.
games
_list
[
index
.
row
()]
name
=
self
.
roleNames
()
.
get
(
role
)
if
name
:
return
getattr
(
student
,
name
.
decode
())
def
roleNames
(
self
)
->
dict
[
int
,
QByteArray
]:
d
=
{}
for
i
,
field
in
enumerate
(
fields
(
Game
)):
for
i
,
field
in
enumerate
(
fields
(
Game
Entry
)):
d
[
Qt
.
DisplayRole
+
i
]
=
field
.
name
.
encode
()
return
d
def
rowCount
(
self
,
index
:
QModelIndex
=
QModelIndex
())
->
int
:
return
len
(
self
.
_list
)
return
len
(
self
.
games
_list
)
def
add_game
(
self
,
game
:
Game
)
->
None
:
def
add_game
(
self
,
game
:
Game
Entry
)
->
None
:
self
.
beginInsertRows
(
QModelIndex
(),
self
.
rowCount
(),
self
.
rowCount
())
self
.
_list
.
append
(
game
)
self
.
games
_list
.
append
(
game
)
self
.
endInsertRows
()
def
clear
(
self
)
->
None
:
self
.
beginInsertRows
(
QModelIndex
(),
self
.
rowCount
(),
self
.
rowCount
())
self
.
_list
=
[]
self
.
games
_list
=
[]
self
.
endInsertRows
()
pass
poetry.lock
View file @
e96ebca9
...
...
@@ -256,6 +256,22 @@ files = [
]
[[package]]
name = "platformdirs"
version = "4.2.2"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.8"
files = [
{file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
{file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
type = ["mypy (>=1.8)"]
[[package]]
name = "pygame"
version = "2.5.2"
description = "Python Game Development"
...
...
@@ -408,6 +424,19 @@ full = ["python-steam-api[all]"]
style = ["codespell[toml] (>=2.2.4)", "isort", "ruff (>=0.1.8)", "toml-sort", "yamllint"]
[[package]]
name = "python-steamgriddb"
version = "1.0.5"
description = "A Python wrapper for SteamGridDB's API"
optional = false
python-versions = "*"
files = [
{file = "python-steamgriddb-1.0.5.tar.gz", hash = "sha256:036db7bb09865da73b40b68cf04fb9675cd18b4908275092d91f37bf16245069"},
]
[package.dependencies]
requests = "*"
[[package]]
name = "requests"
version = "2.31.0"
description = "Python HTTP for Humans."
...
...
@@ -498,4 +527,4 @@ zstd = ["zstandard (>=0.18.0)"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.11,<3.13"
content-hash = "
a19b74dab3d3d62bbdfc01b2ab1b7d011607b45cd0601e65f6a83f16603ce099
"
content-hash = "
8acdd92388f970774431a6b4f12e8e5c0d5d01c0d88d748ae7a4a049b50a5005
"
pyproject.toml
View file @
e96ebca9
...
...
@@ -16,6 +16,8 @@ requests = "^2.31.0"
desktop-parser
=
"^0.1.1"
pygame
=
"^2.5.2"
python-steam-api
=
"^2.0"
python-steamgriddb
=
"^1.0.5"
platformdirs
=
"^4.2.2"
[tool.poetry.group.dev.dependencies]
mypy
=
"^1.9.0"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment