diff --git a/.gitignore b/.gitignore index dbf1d43..7a8fc51 100644 --- a/.gitignore +++ b/.gitignore @@ -145,11 +145,12 @@ dmypy.json *.iml out gen -build.sh +obs-plugin/build.sh # AppImage Build Artifacts *.AppImage AppDir/ build_artifacts/ -linuxdeploy-*.AppImage -appimagetool-*.AppImage \ No newline at end of file +*.so +templates/generated_reset_*.jpg +*.tar.gz \ No newline at end of file diff --git a/README.md b/README.md index e89d1a6..4d80673 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,6 @@ This will generate `AutoSplit64plus-x86_64.AppImage` in the root directory. ### Using the AppImage 1. Make it executable: `chmod +x AutoSplit64plus-x86_64.AppImage` 2. Run it: `./AutoSplit64plus-x86_64.AppImage` + +## Known issues: +Having a filter on the same source as the OBS plugin that has errors (i.e. doesn't have the relevant plugin installed) will cause shared memory to not initialize properly on the plugin side. \ No newline at end of file diff --git a/as64core/capture_shmem.py b/as64core/capture_shmem.py index 75b53bd..337adf4 100644 --- a/as64core/capture_shmem.py +++ b/as64core/capture_shmem.py @@ -15,6 +15,7 @@ class SharedMemoryCapture(object): def __init__(self): self.shmem_handle = None self.shmem = None + self.shmem_fd = None self.width = 0 self.height = 0 self.linesize = 0 @@ -38,13 +39,13 @@ def open_shmem(self): # Linux implementation shm_path = f"/dev/shm/{SHMEM_NAME}" if not os.path.exists(shm_path): - debug_info = f"Path checked: {shm_path}\n" - if os.path.exists("/dev/shm"): - debug_info += f"Contents of /dev/shm: {os.listdir('/dev/shm')}" - else: - debug_info += "/dev/shm directory does not exist!" - - raise Exception(f"Could not find OBS Grabber Plugin!\n\n{debug_info}\n\nPlease make sure the plugin is enabled and OBS is running.") + debug_info = f"Path checked: {shm_path}\n" + if os.path.exists("/dev/shm"): + debug_info += f"Contents of /dev/shm: {os.listdir('/dev/shm')}" + else: + debug_info += "/dev/shm directory does not exist!" + + raise Exception(f"Could not find OBS Grabber Plugin!\n\n{debug_info}\n\nPlease make sure the plugin is enabled and OBS is running.") # We need to open the file to get a file descriptor self.shmem_fd = open(shm_path, "r+b") @@ -91,23 +92,24 @@ def _update_dimensions(self): header = np.frombuffer(shmem_header.read(16), dtype=np.uint32) shmem_header.close() else: - if not hasattr(self, 'shmem_fd') or not self.shmem_fd: - # Try to open if we have the file - if os.path.exists(f"/dev/shm/{SHMEM_NAME}"): - self.open_shmem() - else: - return # Can't update dimensions if not open/exists + if not hasattr(self, 'shmem_fd') or not self.shmem_fd: + print(f"hasattr: {hasattr(self, 'shmem_fd')} shmem_fd: {self.shmem_fd}") + # Try to open if we have the file + if os.path.exists(f"/dev/shm/{SHMEM_NAME}"): + self.open_shmem() + else: + return # Can't update dimensions if not open/exists - # Map header using the file descriptor - # We use the existing FD. - try: - shmem_header = mmap.mmap(self.shmem_fd.fileno(), 16, access=mmap.ACCESS_READ) - shmem_header.seek(0) - header = np.frombuffer(shmem_header.read(16), dtype=np.uint32) - shmem_header.close() - except Exception as e: - print(f"Error reading shmem header: {e}") - return + # Map header using the file descriptor + # We use the existing FD. + try: + shmem_header = mmap.mmap(self.shmem_fd.fileno(), 16, access=mmap.ACCESS_READ) + shmem_header.seek(0) + header = np.frombuffer(shmem_header.read(16), dtype=np.uint32) + shmem_header.close() + except Exception as e: + print(f"Error reading shmem header: {e}") + return new_width = int(header[0]) new_height = int(header[1]) diff --git a/as64core/livesplit.py b/as64core/livesplit.py index a81aec8..2f7b938 100644 --- a/as64core/livesplit.py +++ b/as64core/livesplit.py @@ -6,10 +6,32 @@ except ImportError: win32file = None win32pipe = None + CONNECTIONS = set() + import time +import websockets +import asyncio +import threading from . import config + +async def register(websocket): + CONNECTIONS.add(websocket) + try: + await websocket.wait_closed() + finally: + CONNECTIONS.remove(websocket) + +def websocket_func(): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + ws_server = websockets.serve(register, 'localhost', 5678) + + loop.run_until_complete(ws_server) + loop.run_forever() + loop.close() + # Connect to LiveSplit and return socket def connect() -> object: # Get connection type from config @@ -39,7 +61,13 @@ def connect() -> object: ls_socket.connect((config.get("connection", "ls_host"), config.get("connection", "ls_port"))) return ls_socket except: - return False + try: + # Initialise websocket + server = threading.Thread(target=websocket_func, daemon=True) + server.start() + finally: + return "websocket" + else: return False @@ -48,18 +76,27 @@ def disconnect(ls_socket) -> None: # Check if connection even exists if ls_socket is False: return - ls_socket.close() + try: + ls_socket.close() + except: + # for websocket + return def check_connection(ls_socket) -> bool: - # Check if connection has been established - if (ls_socket == False): - return False - # Check if communication is possible and response is received - if split_index(ls_socket) is False: + try: + if ls_socket == "websocket": + return True + # Check if connection has been established + if (ls_socket == False): + return False + # Check if communication is possible and response is received + if split_index(ls_socket) is False: + return False + else: + return True + except: return False - else: - return True def send(ls_socket, command) -> None: @@ -70,6 +107,8 @@ def send(ls_socket, command) -> None: ls_socket.send(command.encode('utf-8')) # If it is a pipe: else: + if ls_socket == "websocket": + websockets.broadcast(CONNECTIONS, '{"command": "' + command + '"}') if win32file is None: return @@ -80,21 +119,24 @@ def send(ls_socket, command) -> None: raise Exception("LiveSplit connection lost") def split(ls_socket) -> None: - send(ls_socket, "startorsplit\r\n") + send(ls_socket, "splitOrStart" if ls_socket == "websocket" else "startorsplit\r\n") + # send(ls_socket, "startorsplit\r\n") def reset(ls_socket) -> None: - send(ls_socket, "reset\r\n") + send(ls_socket, "reset" if ls_socket == "websocket" else "reset\r\n") def restart(ls_socket) -> None: - send(ls_socket, "reset\r\nstarttimer\r\n") + send(ls_socket, "reset" if ls_socket == "websocket" else "reset\r\nstarttimer\r\n") + if ls_socket == "websocket": + send(ls_socket, "splitOrStart") def skip(ls_socket) -> None: - send(ls_socket, "skipsplit\r\n") + send(ls_socket, "skipSplit" if ls_socket == "websocket" else "skipsplit\r\n") def undo(ls_socket) -> None: - send(ls_socket, "unsplit\r\n") + send(ls_socket, "undoSplit" if ls_socket == "websocket" else "unsplit\r\n") def split_index(ls_socket): diff --git a/build.bat b/build.bat index 4bbb28c..71a5a12 100644 --- a/build.bat +++ b/build.bat @@ -3,8 +3,8 @@ call .venv\Scripts\activate.bat pyinstaller ^ --noconfirm ^ --name "AutoSplit64plus" ^ ---splash "resources\gui\icons\as64plus.png" ^ ---icon "resources\gui\icons\as64plus.ico" ^ +--splash "resources\gui\icons\icon.png" ^ +--icon "resources\gui\icons\icon.ico" ^ --noconsole ^ --clean ^ --noupx ^ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..794d21f --- /dev/null +++ b/build.sh @@ -0,0 +1,20 @@ +source .venv/bin/activate + +pyinstaller \ +--noconfirm \ +--name "AutoSplit64plus" \ +--splash "resources/gui/icons/icon.png" \ +--icon "resources/gui/icons/icon.ico" \ +--noconsole \ +--clean \ +--noupx \ +--contents-directory "libraries" \ +AutoSplit64.py + +cp -r logic dist/AutoSplit64plus/logic +cp -r resources dist/AutoSplit64plus/resources +cp -r routes dist/AutoSplit64plus/routes +cp -r templates dist/AutoSplit64plus/templates +cp defaults.ini dist/AutoSplit64plus/ + +tar -czvf Autosplit64.tar.gz ./dist/AutoSplit64plus diff --git a/build_appimage.sh b/build_appimage.sh index 10a70ba..0e06c92 100755 --- a/build_appimage.sh +++ b/build_appimage.sh @@ -10,7 +10,7 @@ BUILD_ROOT="/tmp/as64_build_$(date +%s)" APP_DIR="$BUILD_ROOT/AppDir" BUILD_DIR="$BUILD_ROOT/build_artifacts" # Path to host Python 3.11 installation to bundle -PYTHON_HOST_PATH="/home/poke/.pyenv/versions/3.11.9" +PYTHON_HOST_PATH="/home/$USER/.pyenv/versions/3.11.9" echo "Build started at $(date)" echo "Build root: $BUILD_ROOT" diff --git a/obs-plugin/autosplit64plus-framegrabber.so b/obs-plugin/autosplit64plus-framegrabber.so deleted file mode 100644 index 8e66ad6..0000000 Binary files a/obs-plugin/autosplit64plus-framegrabber.so and /dev/null differ diff --git a/obs-plugin/src/autosplit64plus-framegrabber.c b/obs-plugin/src/autosplit64plus-framegrabber.c index d174a38..87e9e8c 100644 --- a/obs-plugin/src/autosplit64plus-framegrabber.c +++ b/obs-plugin/src/autosplit64plus-framegrabber.c @@ -66,7 +66,7 @@ static void *filter_create(obs_data_t *settings, obs_source_t *source) { struct filter_data *filter = bzalloc(sizeof(struct filter_data)); if (!filter) { - blog(LOG_ERROR, "Failed to allocate filter data"); + blog(LOG_ERROR, "%s Failed to allocate filter data", PREFIX); return NULL; } @@ -74,14 +74,14 @@ static void *filter_create(obs_data_t *settings, obs_source_t *source) filter->is_valid = !filter_instance_exists; if (!filter->is_valid) { - blog(LOG_WARNING, "AS64+ Frame grabber: Only one instance allowed"); + blog(LOG_WARNING, "%s AS64+ Frame grabber: Only one instance allowed", PREFIX); return filter; // Return filter anyway so properties can show error } filter_instance_exists = true; filter->render = gs_texrender_create(GS_BGRA, GS_ZS_NONE); if (!filter->render) { - blog(LOG_ERROR, "Failed to create texture renderer"); + blog(LOG_ERROR, "%s Failed to create texture renderer", PREFIX); bfree(filter); return NULL; } @@ -98,15 +98,15 @@ static void *filter_create(obs_data_t *settings, obs_source_t *source) * @param data Pointer to the filter data * @return Pointer to the created properties */ -// static bool github_button_clicked(obs_properties_t *props, obs_property_t *property, void *data) -// { -// #ifdef _WIN32 -// system("start " GITHUB_URL); -// #else -// system("xdg-open " GITHUB_URL); -// #endif -// return false; -// } +static bool github_button_clicked(obs_properties_t *props, obs_property_t *property, void *data) +{ +#ifdef _WIN32 + system("start " GITHUB_URL); +#else + system("xdg-open " GITHUB_URL); +#endif + return false; +} // static bool discord_button_clicked(obs_properties_t *props, obs_property_t *property, void *data) // { @@ -138,7 +138,7 @@ static obs_properties_t *filter_properties(void *data) OBS_TEXT_INFO); // Social links - directly in props instead of a group for horizontal layout - // obs_properties_add_button(props, "github_link", "📂 GitHub Repository", github_button_clicked); + obs_properties_add_button(props, "github_link", "📂 GitHub Repository", github_button_clicked); // obs_properties_add_button(props, "discord_link", "💬 Join Discord", discord_button_clicked); // Version and author info - directly use the constants @@ -170,15 +170,15 @@ static bool open_shmem(struct filter_data *filter, uint32_t width, uint32_t heig filter->pBuf = MapViewOfFile(filter->shmem, FILE_MAP_ALL_ACCESS, 0, 0, 16 + width * height * 4); if (filter->pBuf) { filter->shmem_valid = true; - blog(LOG_INFO, "Opened shared memory connection"); + blog(LOG_INFO, "%s Opened shared memory connection", PREFIX); return true; } else { - blog(LOG_ERROR, "Failed to map shared memory: %lu", GetLastError()); + blog(LOG_ERROR, "%s Failed to map shared memory: %lu", PREFIX, GetLastError()); CloseHandle(filter->shmem); filter->shmem = NULL; } } else { - blog(LOG_ERROR, "Failed to create shared memory: %lu", GetLastError()); + blog(LOG_ERROR, "%s Failed to create shared memory: %lu", PREFIX, GetLastError()); } return false; #else @@ -189,12 +189,12 @@ static bool open_shmem(struct filter_data *filter, uint32_t width, uint32_t heig filter->shmem_fd = shm_open(name, O_CREAT | O_RDWR, 0666); if (filter->shmem_fd == -1) { - blog(LOG_ERROR, "Failed to create shared memory: %s", strerror(errno)); + blog(LOG_ERROR, "%s Failed to create shared memory: %s", PREFIX, strerror(errno)); return false; } if (ftruncate(filter->shmem_fd, size) == -1) { - blog(LOG_ERROR, "Failed to resize shared memory: %s", strerror(errno)); + blog(LOG_ERROR, "%s Failed to resize shared memory: %s", PREFIX, strerror(errno)); close(filter->shmem_fd); filter->shmem_fd = -1; return false; @@ -202,7 +202,7 @@ static bool open_shmem(struct filter_data *filter, uint32_t width, uint32_t heig filter->pBuf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, filter->shmem_fd, 0); if (filter->pBuf == MAP_FAILED) { - blog(LOG_ERROR, "Failed to map shared memory: %s", strerror(errno)); + blog(LOG_ERROR, "%s Failed to map shared memory: %s", PREFIX, strerror(errno)); close(filter->shmem_fd); filter->shmem_fd = -1; filter->pBuf = NULL; @@ -210,7 +210,7 @@ static bool open_shmem(struct filter_data *filter, uint32_t width, uint32_t heig } filter->shmem_valid = true; - blog(LOG_INFO, "Opened shared memory connection"); + blog(LOG_INFO, "%s Opened shared memory connection", PREFIX); return true; #endif } @@ -238,9 +238,9 @@ static bool close_shmem(struct filter_data *filter) #ifdef _WIN32 if (filter->shmem) { if (CloseHandle(filter->shmem)) { - blog(LOG_INFO, "Closed the shared memory"); + blog(LOG_INFO, "%s Closed the shared memory", PREFIX); } else { - blog(LOG_ERROR, "Failed to close the shared memory: %lu", GetLastError()); + blog(LOG_ERROR, "%s Failed to close the shared memory: %lu", PREFIX, GetLastError()); } filter->shmem = NULL; } @@ -251,7 +251,7 @@ static bool close_shmem(struct filter_data *filter) // We do not unlink here to allow ensuring other processes can still see it if needed, // but typically we might want to unlink? // For now, mirroring Windows behavior of just closing handle. - blog(LOG_INFO, "Closed the shared memory"); + blog(LOG_INFO, "%s Closed the shared memory", PREFIX); } #endif filter->shmem_valid = false; @@ -283,7 +283,7 @@ static bool copy_to_shared_memory(struct filter_data *filter, uint32_t width, ui memcpy((uint8_t *)filter->pBuf + 16, filter->image_data, width * height * 4); return true; } else { - blog(LOG_ERROR, "Shared memory view is not mapped"); + blog(LOG_ERROR, "%s Shared memory view is not mapped", PREFIX); filter->shmem_valid = false; } @@ -331,7 +331,7 @@ static void filter_render(void *data, gs_effect_t *effect) gs_stagesurface_destroy(filter->staging); filter->staging = gs_stagesurface_create(width, height, GS_BGRA); if (!filter->staging) { - blog(LOG_ERROR, "Failed to allocate staging surface for %dx%d", width, height); + blog(LOG_ERROR, "%s Failed to allocate staging surface for %dx%d", PREFIX, width, height); obs_source_skip_video_filter(filter->context); return; } @@ -341,7 +341,7 @@ static void filter_render(void *data, gs_effect_t *effect) filter->image_data = brealloc(filter->image_data, width * height * 4); } if (!filter->image_data) { - blog(LOG_ERROR, "Failed to allocate image data for %dx%d", width, height); + blog(LOG_ERROR, "%s Failed to allocate image data for %dx%d", PREFIX, width, height); obs_source_skip_video_filter(filter->context); return; } @@ -377,12 +377,12 @@ static void filter_render(void *data, gs_effect_t *effect) } gs_stagesurface_unmap(filter->staging); } else { - // blog(LOG_WARNING, "Failed to map staging surface"); + // blog(LOG_WARNING, "%s Failed to map staging surface", PREFIX); } copy_to_shared_memory(filter, width, height); } else { - blog(LOG_ERROR, "Failed to begin texture render"); + blog(LOG_ERROR, "%s Failed to begin texture render", PREFIX); } gs_texture_t *tex = gs_texrender_get_texture(filter->render); @@ -390,7 +390,7 @@ static void filter_render(void *data, gs_effect_t *effect) // gs_draw_sprite(tex, 0, width, height); obs_source_skip_video_filter(filter->context); } else { - blog(LOG_ERROR, "Failed to get texture from render target"); + blog(LOG_ERROR, "%s Failed to get texture from render target", PREFIX); } } diff --git a/obs-plugin/src/constants.h b/obs-plugin/src/constants.h index 75dcd07..e42ec40 100644 --- a/obs-plugin/src/constants.h +++ b/obs-plugin/src/constants.h @@ -2,5 +2,5 @@ #define PLUGIN_VERSION "\nVersion: 0.3.0" #define PLUGIN_AUTHOR "Author: Davi Be, Poke711, SmolAlli" -#define GITHUB_URL "https://github.com/DaviBe92/AutoSplit64plus" -#define DISCORD_URL "https://discord.gg/VmrQQBpPSK" +#define GITHUB_URL "https://github.com/Poke711/AutoSplit64plusLinux" +#define PREFIX "AS64: " \ No newline at end of file diff --git a/templates/generated_reset_one.jpg b/templates/generated_reset_one.jpg deleted file mode 100644 index f25237f..0000000 Binary files a/templates/generated_reset_one.jpg and /dev/null differ diff --git a/templates/generated_reset_two.jpg b/templates/generated_reset_two.jpg deleted file mode 100644 index 7440106..0000000 Binary files a/templates/generated_reset_two.jpg and /dev/null differ