Add protocol handler support

This lets the user click a link in a web browser to very easily join a Quake 3 multiplayer game. As browser-based matchmaking websites become more popular, this makes it a lot more convenient and simple to play Quake 3 with others.

The links have the following URI format: quake3://connect/example.com:27950. The format has been designed to be flexible to allow more types of links in the future and avoiding having to make a breaking change. At the moment, "connect" is the only supported command.
This commit is contained in:
Stefan Sundin authored and GitHub committed 2023-04-19 13:48:26 +01:00
1 parent 23a85089c2
commit 31c6d2f9d5
7 files changed
+217 -8

No files matched your search

+5
View File
@@ -64,3 +64,8 @@ void Sys_AnsiColorPrint( const char *msg );
int Sys_PID( void );
qboolean Sys_PIDIsRunning( int pid );
#ifdef PROTOCOL_HANDLER
char *Sys_InitProtocolHandler( void );
char *Sys_ParseProtocolUri( const char *uri );
#endif
+119 -1
View File
@@ -642,6 +642,94 @@ void Sys_ParseArgs( int argc, char **argv )
}
}
#ifdef PROTOCOL_HANDLER
/*
=================
Sys_InitProtocolHandler
See sys_osx.m for macOS implementation.
=================
*/
#ifndef __APPLE__
char *Sys_InitProtocolHandler( void )
{
return NULL;
}
#endif
/*
=================
Sys_ParseProtocolUri
This parses a protocol URI, e.g. "quake3://connect/example.com:27950"
to a string that can be run in the console, or a null pointer if the
operation is invalid or unsupported.
At the moment only the "connect" command is supported.
=================
*/
char *Sys_ParseProtocolUri( const char *uri )
{
// Both "quake3://" and "quake3:" can be used
if ( Q_strncmp( uri, PROTOCOL_HANDLER ":", strlen( PROTOCOL_HANDLER ":" ) ) )
{
Com_Printf( "Sys_ParseProtocolUri: unsupported protocol.\n" );
return NULL;
}
uri += strlen( PROTOCOL_HANDLER ":" );
if ( !Q_strncmp( uri, "//", strlen( "//" ) ) )
{
uri += strlen( "//" );
}
Com_Printf( "Sys_ParseProtocolUri: %s\n", uri );
// At the moment, only "connect/hostname:port" is supported
if ( !Q_strncmp( uri, "connect/", strlen( "connect/" ) ) )
{
int i, bufsize;
char *out;
uri += strlen( "connect/" );
if ( *uri == '\0' || *uri == '?' )
{
Com_Printf( "Sys_ParseProtocolUri: missing argument.\n" );
return NULL;
}
// Check for any unsupported characters
// For safety reasons, the "hostname:port" part can only
// contain characters from: a-zA-Z0-9.:-[]
for ( i=0; uri[i] != '\0'; i++ )
{
if ( uri[i] == '?' )
{
// For forwards compatibility, any query string parameters are ignored (e.g. "?password=abcd")
// However, these are not passed on macOS, so it may be a bad idea to add them.
break;
}
if ( isalpha( uri[i] ) == 0 && isdigit( uri[i] ) == 0
&& uri[i] != '.' && uri[i] != ':' && uri[i] != '-'
&& uri[i] != '[' && uri[i] != ']' )
{
Com_Printf( "Sys_ParseProtocolUri: hostname contains unsupported character.\n" );
return NULL;
}
}
bufsize = strlen( "+connect " ) + i + 1;
out = malloc( bufsize );
strcpy( out, "+connect " );
strncat( out, uri, i );
return out;
}
else
{
Com_Printf( "Sys_ParseProtocolUri: unsupported command.\n" );
return NULL;
}
}
#endif
#ifndef DEFAULT_BASEDIR
# ifdef __APPLE__
# define DEFAULT_BASEDIR Sys_StripAppBundle(Sys_BinaryPath())
@@ -690,6 +778,9 @@ int main( int argc, char **argv )
{
int i;
char commandLine[ MAX_STRING_CHARS ] = { 0 };
#ifdef PROTOCOL_HANDLER
char *protocolCommand = NULL;
#endif
extern void Sys_LaunchAutoupdater(int argc, char **argv);
Sys_LaunchAutoupdater(argc, argv);
@@ -724,6 +815,10 @@ int main( int argc, char **argv )
Sys_PlatformInit( );
#ifdef PROTOCOL_HANDLER
protocolCommand = Sys_InitProtocolHandler( );
#endif
// Set the initial time base
Sys_Milliseconds( );
@@ -740,7 +835,22 @@ int main( int argc, char **argv )
// Concatenate the command line for passing to Com_Init
for( i = 1; i < argc; i++ )
{
const qboolean containsSpaces = strchr(argv[i], ' ') != NULL;
qboolean containsSpaces;
// For security reasons we always detect --uri, even when PROTOCOL_HANDLER is undefined
// Any arguments after "--uri quake3://..." is ignored
if ( !strcmp( argv[i], "--uri" ) )
{
#ifdef PROTOCOL_HANDLER
if ( argc > i+1 )
{
protocolCommand = Sys_ParseProtocolUri( argv[i+1] );
}
#endif
break;
}
containsSpaces = strchr(argv[i], ' ') != NULL;
if (containsSpaces)
Q_strcat( commandLine, sizeof( commandLine ), "\"" );
@@ -752,6 +862,14 @@ int main( int argc, char **argv )
Q_strcat( commandLine, sizeof( commandLine ), " " );
}
#ifdef PROTOCOL_HANDLER
if ( protocolCommand != NULL )
{
Q_strcat( commandLine, sizeof( commandLine ), protocolCommand );
free( protocolCommand );
}
#endif
CON_Init( );
Com_Init( commandLine );
NET_Init( );
+43
View File
@@ -34,6 +34,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#import <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
#ifdef PROTOCOL_HANDLER
char *protocolCommand = NULL;
#endif
/*
==============
Sys_Dialog
@@ -115,3 +119,42 @@ char *Sys_StripAppBundle( char *dir )
Q_strncpyz(cwd, Sys_Dirname(cwd), sizeof(cwd));
return cwd;
}
#ifdef PROTOCOL_HANDLER
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end
@implementation AppDelegate
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent
{
NSString *input = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
protocolCommand = Sys_ParseProtocolUri( input.UTF8String );
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
[NSApp stop:nil];
}
@end
char *Sys_InitProtocolHandler( void )
{
[NSApplication sharedApplication];
AppDelegate *appDelegate = [AppDelegate new];
NSAppleEventManager *sharedAppleEventManager = [NSAppleEventManager new];
[sharedAppleEventManager setEventHandler:appDelegate
andSelector:@selector(handleAppleEvent:withReplyEvent:)
forEventClass:kInternetEventClass
andEventID:kAEGetURL];
[NSApp setDelegate:appDelegate];
[NSApp run];
return protocolCommand;
}
#endif