Apparently calling steam's close pipe closes the global pipe,
not just a child dup. Doing so causes subsequent app spoof
launches to fail hard. Don't do that.
Also cleanup achievement GUI rows destruction
Fixes#19
RTFM: all processes must let go of a file for unlink to delete it.
Don't call STEAM_API_SHUTDOWN twice on shutdown.
Make sure destructor is called and add some documentation.
Doing so helps remove some global variables.
Also move the ask_game_refresh lock into the top level calling function
to actually give the right protections. Also update some comments.
Previously, silencing std::asyc's std::future return actually caused
the future to be destructed immediately and wait for the thread to
finish, essentially make the program single threaded when downloading
app icons and making the main GUI laggy.
Additionally, GTK nowadays only allows GUI updates to occur in the
main thread, NOT any other threads.
To fix both these issues, refactor the app icon downloads to occur in
threads fired off by the load_items_idle loop and have the main thread
track when the threads finish to go update the GUI.
Similarly, fire off the refresh_owned_apps loop in a separate thread and
wait for (but do not block for) its completion.
Various changes and cleanups:
- Make some functions static so they're more usable in async operations.
- Remove wait_cycles loop: Work is properly offloaded from the main loop
now, and it just slows down launch time now.
- Transform some more functions to use proper AppId_t
- Remove now-unused observer-subject pattern between Downloader and
SteamDAO
- Remove now-ununsed semaphore:
Atomic access not needed for single threaded main loop)
In the downloader, silencing async's future return is a bad idea
because the future is immediately destructed, and the main thread
waits on it, making the program single-threaded.
Instead of async, just use a much simpler C++ detached thread to
go do the work for us. Trying to use gdk_threads_add_idle_full to get
threading functionality didn't really work for offloading computation
from the main thread, but this C++ threading approach works very
well.
Implemenent a semaphore because C++ doesn't have a native
one and use it to limit simultaneous downloads to 10.
Otherwise when tons of downloads launch, network resolution
can start failing.
Also end users with tons of games could potentially launch
thousands of threads, which isn't a great idea.
Use a mutex on updating the main GUI because modifying the GUI
concurrently is dangerous (and pixbuf would start spuriously
failing)
Now in practice, the GUI is very usable during icon loading time.
App icons were not downloaded asynchronously, so on first run
of SamRewritten, the main window would be completely frozen
for a long time (>1 min). So, download app icons asynchronously.
Now the main window is usable (though still a bit stuttery) and the
game rows are presented before before all icons are downloaded.
Implement a basic async loading of games. This allows the
gtk_main loop to progress and show the main window more
quickly so the program feels more responsive.
App names parsing and app_is_owned loop are not currently
split out, so the load_items_idle loop function relinquishes
some cycles at the beginning to allow the main window to show
quickly.
Misc:
- Use gtk_builder_new_from_file to do all error handling
- Improve error handling when clicking invalid app:
make it so an invalid callback (likely resulting from an app
witout achievements) does not hang the program
- Remove server/client debug logs
XDG Base Directory Specification is now followed.
See globals.h for details.
With XDG respected, steamclient.so is dynamically
detected with a best-effort approach. SamRewritten
must dynamically hook into it at runtime, and must
use the running version, so now the git repo doesn't
need to be updated everytime steamclient.so updates.
Using the same steamclient.so for both app detection
and game launching causes stale shared object state to be
present in the child upon fork, so explictly unload the
parent's steamclient.so so we can get a clean one.
32bit and SteamBeta detection is not implemented at
this time, but can be in the future if there is interest.
Misc:
Disallow running as root
Wrap directory creation
Move most directory creation to main()
Fix some typos
With this change, the socket implementation is feature-complete.
Create AchievementChange_t to encapsulate minimal info needed to
change an achievement
Create populate_achievement function to bring together common
functionality of populating achievements in GUI
Implement MySteam::commit_changes()
Implement the rest of the logic needed for retrieving
achievements via a JSON interface. Use YAJL for this
encoding and decoding achievements and requests.
Pull out most of the YAJL functionality into YAJL helpers.
The YAJL wrapping and abstraction can still be done better,
but now at least a good start is in place.
Upgrade Achievement_t to use C++ types
Delete GameEmulator.h/c since the sockets are feature-complete enough
Next commit will implement storing achievements via JSON interface.
Implemented all of get_achievements on server side and client side, ack, and
quit_game.
achievement_t to json transformation needs to be standardized, and will be done next
commit.
Convert CameEmulator child side to GameServer
Convert gameEmulator parent side into MySteam somewhat
Define basic protocol for client-server communication. This protocol
is added as actions in. types/Actions.h. These still need to be
finalized and implemented in yajl.
Add back method of retrieving cached owned games and
get_user_steamId3 for now because the new way is far too slow.
Improve control flow over the GameEmulator implementation.
Less inherent and more explicit behavior.
Simplify buffer logic to just use NULL terminators.
Then JSON requests can be sent as plaintext json strings.
Add some NULL checks.
Change the parent-child communication to only send one signal
when the parent requests the child to store changes. Doing so is
necessary because having multiple signals in flight is dangerous
and can lead to losing signals (because pending signals are only
indicated with a binary flag). Subsequent changes should still
move actions out of the signal handler.
Instead of multiple signals, only one signal is sent with the
number of changes the child should act on. Then the child
will take appropriate actions based on the number of changes
and also commit the changes and update the parent inherently.
This fixes inconsistent behavior when storing changes to Steam.
Also add some error checking and remove spurious check that
would give a warning when the achievements list had already
been populated. The achievements list is properly destroyed
before reusing the variable, so there's no need for the check.
Parent-child communication is currently unstable because there
are some underlying race conditions that lose signals and cause
the main window to not update correctly. As a first step towards
fixing this, coalesce combine both SIGUSR callbacks into one so
they can be moved together in subsequent changes.
lock_achievement/relock_achievement currently only change the
achivement status locally and these changes are only committed
to the server when the game exits.
This addition makes the store stats button actually commit the
changes immediately. Doing so leads to better interaction with
the user.
Also implement cleanup TODO.
If a parent is writing to a pipe and a child empties the pipe, the
child read can return before the entire struct is read. The program
would then continue on with a corrupt struct.
Implement a wrapper to guarantee that all bytes are read, barring
any errors. This fix unlocks achievements much more reliably.
write isn't as much of a problem because it will always write
all bytes unless the buffer is full, which isn't generally a problem
here.