Code Bank - share useful code!

Samuel Venable

Time Killer
Similar to the VBForums code bank, we could seriously use one. Just a place to share and talk about useful code snippets, whether raw GML or stuff useful to build upon for native extensions. I'm only putting this in the advanced forum because native extension code is permitted to be shared here. Not all code snippets must be written by advanced users! Most notably, GML can be shared here--and if it's useful--doesn't matter if it was easy to write. Please give explanations of what your code does and how to use it. This'll be a good resource.

Just post it!

Have questions about a code snippet? Do not clog or distract the goodness of this topic. PM the post's author, and hope they get back to you. :)

------------------------

I'll start.

For example, here's a snippet I wrote for a much larger project recently, but is useful for much more than being shared as an extension on its own; I'm using it quite a lot for a Linux re-write of my Dialog Module extension:

main.cpp
Code:
/*

 MIT License

 Copyright © 2019 Samuel Venable

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

*/

#include <cstdio>
#include <cstdlib>
#include <string>

using std::string;

#define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))

EXPORTED_FUNCTION char *evaluate_shell(char *command) {
  char *buffer = NULL;
  size_t buffer_size = 0;

  string str_command = command;
  string str_buffer = "";

  FILE *file = popen(str_command.c_str(), "r");
  while (getline(&buffer, &buffer_size, file) != -1)
    str_buffer += buffer;

  free(buffer);
  pclose(file);

  if (str_buffer.back() == '\n')
    str_buffer.pop_back();

  static string str_result;
  str_result = str_buffer;

  return (char *)str_result.c_str();
}
Again, please give explanations of what your code does and how to use it. Allow me to start by explaining the code above. It evaluates shell scripts, while similar to my "famous" Execute Shell extension, not only can it execute from the shell, it can also read any output from it (not including errors, but that could easily be added with a small tweak). Compile it with the G++ GNU C++ Compiler, using the following in any Unix terminal:

Code:
cd /Path/To/EvaluateShell/
g++ -c -std=c++11 main.cpp -fPIC -m64
g++ main.o -o EvaluateShell.x64/EvaluateShell.so -shared -fPIC -m64
Replace "/Path/To/EvaluateShell/" with the path to a folder containing the C++ code file and make sure that file is named "main.cpp". You will need an output folder in that same directory named "EvaluateShell.x64". Notice the x64, this will be for 64-bit builds and requires building on a 64-bit Unix OS (whether macOS, Linux, BSD, Solaris, or whatever).

If you want 32-bit support of this extension sample, the same rules apply. Install G++ on your OS as needed, cd to the correct path, and run the commands one line at a time on your terminal application. Create the same folder, but this time have it named "EvaluateShell.x86". Depending on your OS, you will need the proper file extension for the shared library. Linux as you can see is *.so, macOS is *.dylib, Windows is *.dll and so forth:

Code:
cd /Path/To/EvaluateShell/
g++ -c -std=c++11 main.cpp -fPIC -m32
g++ main.o -o EvaluateShell.x86/EvaluateShell.dylib -shared -fPIC -m32
Notice in the above terminal commands snippet, this time I used the dylib extenson - "EvaluateShell.dylib" - this is good for compiling on macOS right out of the box as soon as you have G++ via the HomeBrew package manager. On Linux you can compile the code in the snippet previous to the one directly above and you can use the extension via GameMaker Studio 1.4, as the Linux runner is 32-bit in all versions of Studio prior to 2.x. Mac is 64-bit in both runners as of 1.49999 to Studio 2.x onward.

Should you compile for Studio 2 on Linux, you will need to use the -m64 flag instead of -m32, via modifying the terminal snippet, depending on which one of the two you copy/paste. Create an extension in Studio from the Extensions node of the resource tree in the IDE. Get the library file imported and create the function. Make sure the internal and external names of the function are "evaluate_shell". The return value must be a string. Add an argument and make it a string. The help line should be:

Code:
evaluate_shell(command)
Now you are ready to test it. command is the terminal command or shell script contents you wish to execute and evaluate.

------------------------

Have fun and I hope this topic gets a lot of love.

Edit:

I discovered Windows does not have the getline() POSIX function, so to port this properly, I'll need to write my own. I'll share it here when I have a thoroughly tested and working version of that.
 
Last edited:

Tthecreator

Your Creator!
The first script I always import into a new project is this:
Code:
///sdbm(var1,var2)
/*
Stands for "show debug message" and writes a log line showing any values given to it.(Will be shown within game maker's compiler log)
Multiple arguments of any type can be passes to this script and every argument will be seperated by a "#" symbol.
*/
var str=""
for(var i=0;i<argument_count;i++){
str=str+string(argument[i])+"#"
}
show_debug_message(str)
It will draw any inside your compile form. The nice thing is that multiple values can be given. These values will be seperated by a #.
An example would be:
sdbm("updated position",id,x,y).
The output could be:
update position#5#100#234#
 

Tsa05

Member
Each of the scripts below were devised while working on a dialog/VN engine, and now I routinely go back to them in other projects.


array_delete(array, pos)
Deletes a position from an array. Srsly, how is this not a built-in thing?
Code:
///    @description     array_delete(array, pos)
///    @arg    {array}    array    The array to delete from
///    @arg    {real}    pos        The position to delete
///    @returns a new array or -1 when the array is empty
var a = argument0;
var i = argument1;
var r = -1;
var L = array_length_1d(a);
if(L>1){
    r=array_create(L-1);
    array_copy(r, 0, a, 0, i);
    array_copy(r, i, a, i+1, L-i+1);
}
return r;

array_find(array, value)
Finds the index of value in array, or returns -1, because srsly, how is this not a built-in thing?
Also, note the hilarity of the length of this script, while reading the GMS 2.2 continuity thread. See how this script tediously tests its way through amusingly weird problems with equivalency.
Code:
///    @description    array_find(array, value)
///    @args    {array}    array    The array to search
///    @args    {any}    value    The value to search for
/*
*    Searches a given array for the value indicated.
*    Returns -1 or the first index located.
*/
var a = argument[0];
var v = argument[1];
var retval = -1;
if(!is_array(a)){
    return retval;
}
var type = typeof(v)
for(var z=0;z<array_length_1d(a);z+=1){
    var av = a[z];
    var avtype = typeof(av);
    if(avtype==type){
        if(av==v){
            retval = z;
            return retval;
        }
    }
}
return retval;

array_insert_1d(array, index, value)
Inserts a value into an array because srsly, how is this not a built-in thing? Sensing a theme here?
Code:
///    @description    Returns the modified array
///    @arg    {real}    array    The arrey to insert into
///    @arg    {real}    index    Where to insert
///    @arg            value    What to add to the array
///    @returns    {real}    Array with the newly inserted value

var a = argument[0];
var p = argument[1];
var d = argument[2];

if(!is_array(a)){
    a[0] = d;
}else{
    var L = array_length_1d(a);
    var r = array_create(1);
    array_copy(r, 0, a, 0, p);
    r[p] = d;
    array_copy(r, p+1, a, p, L-p+1);
}
return r;

array_is_ok(array, index)
Simply performs that oft-needed check of whether an index is greater than or equal to zero and also within the length bounds of the array.
Code:
///    @desc    Returns true if a valid index within a valid array is found
///    @arg    {real}    array
///    @arg    {real}    index
var ary = argument[0];
var ind = argument[1];

return (ind>=0 && is_array(ary) && ind<array_length_1d(ary));

array_to_string(array, token)
Turns an array into a string, adding token after each entry
Code:
/// @description array_to_string(array, token)
/// @arg {array}    array    The array to concatenate
/// @arg {string}    token    Character to tokenize with
var a = argument[0];
var t = argument[1];
var r = "";
if(!is_array(a)){ return a; }
for (var z=0; z<array_length_1d(a); z+=1){
    r+=string(a[z])+t;
}
return r;

string_to_array(string, token)
Break up a string into array parts using a token separator
Code:
/// @desc string_to_array(string, token)
/// @arg {string} string The string to tokenize
/// @arg {string} token The character to use as a token
/*
*   Turn a tokenized string into an array of values
*/

var s = argument0;
var t = argument1;
var a = 0;

var n = string_count(t,s);
if(string_char_at(s,string_length(s))!=t){
    n+=1;
}

for(var z = 0;z<n;z+=1){
    var p = string_pos(t,s);
    if(!p) p=string_length(s)+1;
    a[z] = string_copy(s,1,p-1);
    s = string_delete(s,1,p-1+string_length(t));
}
return a;

debug_array(array)
Print the array to the debugger. Sanity restored.
Code:
/// debug_array(array)
var array = argument[0];
if(!is_array(array)){
    show_debug_message("Not an array! "+string(array));
}
var height = array_height_2d(array);
if(height>0){ //2D
    for(var row=0; row<height; row+=1){
        var txt = "| ";
        for(var col=0; col<array_length_2d(array, row); col+=1){
            txt += string(array[row, col])+" | ";
        }
        show_debug_message(txt);
    }
}else{
    var txt = "| ";
    for(var col=0; col<array_length_1d(array); col+=1){
        txt += string(array[col])+" | ";
    }
    show_debug_message(txt);
}

color_sub(colorName)
Accepts a string name of GameMaker's built-in colors, returns the value of that color. Useful for cases where you might be loading color info from an external file. Extra useful in cases where GMS adds made-up precision to numbers when writing data to files ;)
Code:
///    @arg    {string}    colorName        The name of the GMS color to use
///    @returns    {real}    colorVal        Game Maker encoded color or -1

var c = string_lower(argument[0]);

switch (c){
    case "aqua":
        return c_aqua;
    break;
    case "black":
        return c_black
    break;
    case "blue":
        return c_blue;
    break;
    case "dkgray":
        return c_dkgray;
    break;
    case "fuchsia":
        return c_fuchsia;
    break;
    case "gray":
        return c_gray;
    break;
    case "green":
        return c_green;
    break;
    case "lime":
        return c_lime;
    break;
    case "ltgray":
        return c_ltgray;
    break;
    case "maroon":
        return c_maroon;
    break;
    case "navy":
        return c_navy;
    break;
    case "olive":
        return c_olive
    break;
    case "orange":
        return c_orange
    break;
    case "purple":
        return c_purple
    break;
    case "red":
        return c_red;
    break;
    case "silver":
        return c_silver;
    break;
    case "teal":
        return c_teal;
    break;
    case "white":
        return c_white;
    break;
    case "yellow":
        return c_yellow;
    break;
}
return -1;

debug_console(val1, val2, ... )
An old debug_message replacement script. Could use some minor updating, but you use it in place of the regular debug message command and it automatically adds the TIME, OBJECT, EVENT, and all passed arguments cast to strings, separated by spaces.
Code:
/// debug_console(val1,val2,...)
var o=object_get_name(object_index)
var e="";
var s="";
var i=0;
repeat (argument_count) {
    s+=" "+string(argument[i++]);
}
switch event_type{
case ev_create: e="Create";break;
case ev_destroy: e="Destroy";break;
case ev_alarm: e="Alarm";break;
case ev_step: e="Step";break;
case ev_keyboard: e="Keyboard";break;
case ev_mouse: e="Mouse";break;
case ev_collision: e="Collision";break;
case ev_other:
    switch event_number{
    case 63:
        e="Async";
    break;
    default:
        e="Other";
    }
break;
case ev_draw: e="Draw";break;
case ev_keyrelease: e="KeyRelease";break;
default: e="Room";o="noone";}
show_debug_message(date_time_string(date_current_datetime())+" | "+o+" | "+e+" |"+s);

draw_color_picker(x, y, w, h, ds_list);
You make a ds list containing color info in the form of [R,G,B] arrays. This script draws a series of color picker boxes with those colors, fitting within the width and height desired. The script also returns the index of the color that you're currently hovering over, so that you can store it to an instance variable and use it on clobal mouse click, etc. Returns -1 if you're not hovering over a color.
Code:
/// @description        draw_color_picker(x,y,w,h, ds_list)
/// @param    {real}    x            Starting x-coordinate
/// @param    {real}    y            Starting y-coordinate
/// @param    {real}    w            Width of picker
/// @param    {real}    h            Height of picker
/// @param    {real}    perRow        How many per row to draw
/// @param    {real}    colorsList    A ds_list containing [r,g,b] data
/*
*    Draw a series of bordered boxes using a defined border color and
*    a ds_list of Game Maker color values
*
*    Returns:
*        Real: An array of RGB color values
*/
var hilightColor = c_yellow;
var borderColor = c_black;

var px = argument[0];
var py = argument[1];
var pw = argument[2];
var ph = argument[3];
var pr = argument[4];
var pc = argument[5];

if(!ds_exists(pc,ds_type_list)) return -1;
var pn = ds_list_size(pc);
var preColor = draw_get_color();
// How small is a side, to make it all fit...
var p = 3;
var s = pw/pr-p;
var retVal = -1;
var dx = px; var dy = py;
for(var z = 0; z< pn; z+=1){
    draw_set_color(borderColor);
    if(point_in_rectangle(mouse_x,mouse_y, dx, dy, dx+s, dy+s)){
        retVal = pc[|z];
        draw_set_color(hilightColor);
    }
    draw_rectangle(dx, dy, dx+s, dy+s, 0);
    var c = pc[|z];
    if(is_array(c) && array_length_1d(c)>=3){
        draw_set_color(make_color_rgb(c[0], c[1], c[2]));
    }
    draw_rectangle(dx+1, dy+1, dx+s-1, dy+s-1, 0);
    dx+=s+p;
    if(dx>=px+pw){
        dx = px; dy+=s+p;
    }
}
draw_set_color(preColor);
return retVal;

draw_dashed_line(x1, y1, x2, y2, width, spacing/size, dashType)
Draws a dashed (0) or dotted (1) line between the given points.
Code:
///    @arg    x1            x1
///    @arg    y1            y1
///    @arg    x2            x2
///    @arg    y2            y2
///    @arg    width        Width of line
///    @arg    dashSize    Spacing/size of dash
///    @arg    dashType    Dashed or dotted
var x1 = argument0;
var y1 = argument1;
var x2 = argument2;
var y2 = argument3;
var w  = argument4;
var size = argument5;
var type = argument6;

var len = point_distance(x1,y1,x2,y2) div size;
var dir = point_direction(x1,y1,x2,y2);
var a = lengthdir_x(size,dir);
var b = lengthdir_y(size,dir);
for(var i=0; i<len; i++){
    if !(i & 1){
        if(type==0){
            draw_line_width(x1+a*i, y1+b*i, x1+a*(i+1), y1+b*(i+1), w);
        }else if(type==1){
            draw_circle(x1+a*i, y1+b*i, w, 0);
        }
    }
}

DS CHECKING: Since there's some funny ways that things can go wrong, especially with loading external JSON, I found myself typing out the same darned silly long checking statements over and over. For example, attempting to load a list and then using the ds_exists function will crash GameMaker if the result was "undefined."
So there's scripts now. There's never a guarantee that a datastructure is actually what you think it is, but these help...

ds_is_list(listID)
Is the value stored in this variable actually a ds_list?
Code:
///@description    ds_is_list(listID)
///@param    listID    Check whether this is a list
var l = argument[0];

return !(is_undefined(l) || is_string(l) || !ds_exists(l,ds_type_list));

ds_is_map(mapID)
Code:
///@description    ds_is_map(mapID)
///@param    mapID    Check whether this is a map
/*
*    Returns whether the input is a map
*/
var m = argument[0];

return !(is_undefined(m) || is_string(m) || !ds_exists(m,ds_type_map));

DS_List_adds: The ds_ functions and accessors are often decent, but in cases where you're dealing with JSON, you're going to find yourself repeating certain extra functions a stupid number of times. GMS has ds_map_add_map() and ds_map_add_list() for adding data structures to maps. But is missing these others:

Warning:
These functions are magical and also poison. By marking a list item as a data structure, GameMaker will automatically clean up (destroy) child structures when the parent structure is destroyed. This is a blessing for memory management, and a curse if you needed those lists elsewhere ;D

ds_list_add_list(id, list)
Add a list to a list, marking it as a list.
Code:
///    @arg    {real}        id        The list to add to
///    @arg    {real}        list    The list to add
/*
*    Put a list into a list properly; JSON compliant
*    If the containing list is destroyed,
*    this list destroys with it.
*
*/
var a = argument[0];
var b = argument[1];

ds_list_add(a, b);
ds_list_mark_as_list(a, ds_list_size(a)-1);

ds_list_add_map(id, map)
Add a map to a list, marking it as a map
Code:
///    @arg    {real}        id        The list to add to
///    @arg    {real}        map        The map to add
/*
*    Put a map into a list properly; JSON compliant
*    If the containing list is destroyed,
*    this map destroys with it.
*/

var a = argument[0];
var b = argument[1];

ds_list_add(a, b);
ds_list_mark_as_map(a, ds_list_size(a)-1);

ds_list_lookup(list, index, default)
Especially when dealing with external data, you'll need to look up a value, then examine it for validity and possibly substitute a default. All in one script, here.
Code:
/// @description ds_map_lookup(list, index, default);
///    @arg    {ds_list}        list        The Map to search
///    @arg    {real}            index        The key to look up
///    @arg                    default        The value to return if not found
var L = argument[0];
var k = argument[1];
var d = argument[2];
if(!is_undefined(L) && ds_exists(real(L), ds_type_list) && k>=0 && k<ds_list_size(L)){
    d = L[|k];
}
return d;

ds_map_lookup(map, key, default)
Especially when dealing with external data, you'll need to look up a value, then examine it for validity and possibly substitute a default. All in one script, here.
Code:
/// @description ds_map_lookup(map, key, default);
///    @arg    {ds_map}        map        The Map to search
///    @arg    {real,string}    key        The key to look up
///    @arg    {real,string}    default The value to return if not found
var m = argument[0];
var k = argument[1];
var d = argument[2];
if(!is_undefined(m) && ds_exists(real(m), ds_type_map) && ds_map_exists(m,k)){
    d = ds_map_find_value(m,k);
}
return d;

is_in(value, test1, test2, test3, ...) OR
is_in(value, array)
Described in function, and I submitted it to the gmlscripts.com forums, too.
Basically, if I want to test whether x, y, or z is equal to a value, that requires a longish IF statement.
If I'm ok with potentially sacrificing some short-circuit probabilities, I can use this:
if is_in(value, x, y, x) OR
if is_in(value, [x, y, z])
(Which can be especially useful if the array is populated elsewhere)
Code:
///    @description        Returns whether a value is in the supplied set of arguments
///    @function            is_in( value, test0, [test1], [...] )
///    @param                value    The value to search for
///    @param                test0    Value to compare to
///    @param                [test1]    Add parameters as needed to extend the set
///    @returns    {bool}    True when value was found in set
/*
*    Suppose we want to test whether x, y, or z is equal to value.
*    GameMaker:
*        if(x==value || y==value || z==value)
*    Python:
*        if value in {x, y, z}
*    This script shortens what you'd have to type in GameMaker like so:
*        if is_in(value, x, y, z)
*/
// Created for SLIDGE engine
// Submitted to GMLscripts.com/license

var test = argument[0];
var argz = 0;
for(var z=1; z<argument_count; z+=1){
    argz = argument[z];
    if(is_array(argz)){
        for(var i=0; i<array_length_1d(argz); i+=1){
            if(test == argz[i]){
                return true;
            }
        }
    }else{
        if(test == argument[z]){
            return true;
        }
    }
}
return false;

I think those are the chief ones I keep reusing...
 
Last edited:

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
array_delete(array, pos)
Deletes a position from an array. Srsly, how is this not a built-in thing?
This can be done with two array_copy calls and an array_create call, no need to loop over array while reallocating it length-1 times.
Similar goes for array_insert_1d.

array_find(array, value)
Finds the index of value in array, or returns -1, because srsly, how is this not a built-in thing?
Also, note the hilarity of the length of this script, while reading the GMS 2.2 continuity thread. See how this script tediously tests its way through amusingly weird problems with equivalency.
You could use typeof() instead of manual checking (which has you miss a handful of types), but also you could just check if value is a string and check for strings in that case (and anything but strings if it's not).

An old debug_message replacement script. Could use some minor updating, but you use it in place of the regular debug message command and it automatically adds the TIME, OBJECT, EVENT, and all passed arguments cast to strings, separated by spaces.
As of past few years, debug_get_callstack offers better position information (I also wrote a post about this)

ds-related functions won't reliably save you from trouble (e.g. if your JSON contains a number instead of an array, it might hit a valid DS id), but do what you may
 

kraifpatrik

Member
A funny little macro that ensures that the code after it in a script / object (not instance!) event is executed only once.
Code:
#macro PRAGMA_ONCE \
    do \
    { \
        if (!variable_global_exists("__pragmaOnce")) \
        { \
            global.__pragmaOnce = ds_map_create(); \
        } \
        var _cs = debug_get_callstack(); \
        var _cr = _cs[0]; \
        if (ds_map_exists(global.__pragmaOnce, _cr)) \
        { \
            exit; \
        } \
        global.__pragmaOnce[? _cr] = 1; \
    } \
    until (1)

EDIT: Oh god dammit, I accidentaly deleted the previous snippets... :( I will add them again later
 
Last edited:

Tsa05

Member
@YellowAfterlife Great suggestions; I'd assumed that GameMaker's copying was essentially equivalent, but they appear to be a bajillion times speedier. I've updated array_delete, array_insert_1d, and array_find in the OP accordingly, so that it looks like you're crazy ;D
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Simple script for getting a 3D vector from a 2D window coordinate, works for both perspective and orthographic projections:
Code:
/// @description convert_2d_to_3d(camera, x, y)
/// @param camera
/// @param x
/// @param y
/*
Transforms a 2D coordinate (in window space) to a 3D vector.
Returns an array of the following format:
[dx, dy, dz, ox, oy, oz]
where [dx, dy, dz] is the direction vector and [ox, oy, oz] is the origin of the ray.

Works for both orthographic and perspective projections.

Script created by TheSnidr
*/
var camera = argument0;
var _x = argument1;
var _y = argument2;

var V = camera_get_view_mat(camera);
var P = camera_get_proj_mat(camera);

var mx = 2 * (_x / window_get_width() - .5) / P[0];
var my = 2 * (_y / window_get_height() - .5) / P[5];
var camX = - (V[12] * V[0] + V[13] * V[1] + V[14] * V[2]);
var camY = - (V[12] * V[4] + V[13] * V[5] + V[14] * V[6]);
var camZ = - (V[12] * V[8] + V[13] * V[9] + V[14] * V[10]);

if (P[15] == 0)
{    //This is a perspective projection
    return [V[2]  + mx * V[0] + my * V[1],
            V[6]  + mx * V[4] + my * V[5],
            V[10] + mx * V[8] + my * V[9],
            camX,
            camY,
            camZ];
}
else
{    //This is an ortho projection
    return [V[2],
            V[6],
            V[10],
            camX + mx * V[0] + my * V[1],
            camY + mx * V[4] + my * V[5],
            camZ + mx * V[8] + my * V[9]];
}
And 3D to 2D:
Code:
/// @description convert_3d_to_2d(x, y, z, viewMat, projMat)
/// @param x
/// @param y
/// @param z
/// @param viewMat
/// @param projMat
/*
    Transforms a 3D coordinate to a 2D coordinate. Returns an array of the following format:
    [x, y]
    where x and y are between (0, 0) (top left) and (1, 1) (bottom right) of the screen
    Returns [-1, -1] if the 3D point is behind the camera

    Works for both orthographic and perspective projections.
 
    Script created by TheSnidr
    www.thesnidr.com
*/
var _x = argument0;
var _y = argument1;
var _z = argument2;
var V = argument3;
var P = argument4;

var w = V[2] * _x + V[6] * _y + V[10] * _z + V[14];
if w <= 0
{
    return [-1, -1];
}
var cx, cy;
if (P[15] == 0)
{    //This is a perspective projection
    cx = P[8] + P[0] * (V[0] * _x + V[4] * _y + V[8] * _z + V[12]) / w;
    cy = P[9] + P[5] * (V[1] * _x + V[5] * _y + V[9] * _z + V[13]) / w;
}
else
{    //This is an ortho projection
    cx = P[12] + P[0] * (V[0] * _x + V[4] * _y + V[8]  * _z + V[12]);
    cy = P[13] + P[5] * (V[1] * _x + V[5] * _y + V[9]  * _z + V[13]);
}
return [.5 + .5 * cx, .5 - .5 * cy];
 
Last edited:

Pfap

Member
Here's a script for finding the greatest common divisor of two numbers.

Code:
// gcd(a,b)
// constraint: a>0 and b>0
var a,b;
a = argument0;
b = argument1;
//It will run until the return. while true == true{
while true {
    a = a % b;
    if a = 0{ return b };
    b = b % a;
    if b = 0{ return a };
}
I discovered the usefulness of this script while working out a way to display point meters. It could be useful for health bars or other in game bars; basically, anytime you want to display a fraction of a whole.

Lets say you have a level progress bar that takes 1000 points to fill and you want it to be displayed in the center of your room and for this example, the bar already has 500 points. The problem I ran into is that my room_width is 432 and I wanted 16 pixels of padding on either side.
So, we know that the full bar is 1000 points, which leaves us with 1 unkown that can be though of as a fraction algebra problem.
500/1000 and x/(room_width-32)

We need to reduce before we can divide and multiply to find x, which is what the above script will do for you.


Here is some code depicting a progress bar 1/2 full.
Code:
bar_progress = 500;
bar_length = 1000;


//reduce the current amount of bar_progress/bar_length
var divisor = gcd(bar_progress,bar_length);
var reduced_progress = bar_progress/divisor;
var reduced_bar = bar_length/divisor;

//now get it into the amounts I can use to draw
full_length = (room_width-32);//32 for padding
var multiplier = full_length/reduced_bar;
draw_progress = multiplier*reduced_progress;


Now draw progress will be the length of half of (room_width-32) or 200/400. Which is what I wanted a 200 pixel length bar to draw on top of a 400 pixel bar of a different color, thus depicting progress of 50%.
It is really easy to use any values to build dynamic meters and bars to display info and can of course be easily used with sprite_width for any custom bars or even set values and draw_line for simple enemy health bars that will not pollute the game screen. For instance, if you have enemy sprites that are 32 pixels wide and you want to cram a 1000 point health bar above them; you would just replace the full_length variable with a value you set, in this case 32.

I guess it could almost be thought of as a unit converter for points to pixels.





Commonly known as the Euclidean algorithm and the original script I discovered here:
https://forum.yoyogames.com/index.php?threads/greatest-common-factor-script.25245/
 
Last edited:

Samuel Venable

Time Killer
This is a continuation from the OP, where I demonstrated how to execute and evaluate shell scripts with a GameMaker extension. After you have compiled main.cpp into a DYLIB for macOS, and done everything else instructed in the OP, you are ready for trying out the stuff outlined in this post.

If you would like to skip this tutorial completely, you may download the finished product here.

(Note: the app in the ZIP included file still needs to be code-signed).


First, you will need to download DialogModule for Mac Scripting. Download and extract the ZIP archive from that link. Under the extracted folder named "DialogModule" there is the entire source code for the DialogModule Mac Executable. Don't be so focussed on that folder - it's just there for people with experience in C++ and Objective-C who want to modify or expand the software I'm providing free in that download. MIT License.

Where you should first place your focus, however, is on the extracted folder, "dlgmod64" where you will find a mac app that can be used for scripting, named "dlgmod64.app". This file will execute the shell script commands you feed it at runtime, and this example will open a series of dialog boxes that normally are not available in GameMaker Studio, such as an NSAlert.

Create a new project in GameMaker, import the extension you previously had made, and paste this code into the create event of an empty, first room:

For GameMaker Studio 1.4 users:
Code:
zip_unzip(working_directory + "dlgmod64.zip", game_save_id + "dlgmod64/");
shellscript_evaluate('chmod 777 "' + game_save_id + 'dlgmod64/dlgmod64.app/Contents/MacOS/dlgmod64"');
var prefix = '"' + game_save_id + 'dlgmod64/dlgmod64.app/Contents/MacOS/dlgmod64" ', str;

str = shellscript_evaluate(prefix + '--show-message "Hello World" "DialogModule"');
shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--show-message-cancelable "Hello World" "DialogModule"');
shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--show-question "Yes or no?" "DialogModule"');
shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--show-question-cancelable "Yes, no, or cancel?" "DialogModule"');
shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--show-attempt "Hello World!" "Error - DialogModule"');
shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--show-error "Hello World!" 0 "Error - DialogModule"');
if (str == "") { game_end(); exit; } shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-string "Enter a string:" "Hello World!" "DialogModule"');
if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-password "Enter a string password:" "Hello World!" "DialogModule"');
if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-integer "Enter an integer:" 0 "DialogModule"');
shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-passcode "Enter an integer passcode:" 0 "DialogModule"');
shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

var filter = "Sprite Images (*.png *.gif *.jpg *.jpeg)|*.png;*.gif;*.jpg;*.jpeg|Background Images (*.png)|*.png|All Files (*.*)|*.*";

str = shellscript_evaluate(prefix + '--get-open-filename "' + filter + '" "Select a File"');
if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-open-filename-ext "' + filter + '" "Select a File" "" "Open Ext"');
if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-open-filenames "' + filter + '" "Select Files"');
if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-open-filenames-ext "' + filter + '" "Select Files" "" "Open Ext"');
if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-save-filename "' + filter + '" "Untitled.png"');
if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-save-filename-ext "' + filter + '" "Untitled.png" "" "Save As Ext"');
if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-directory ""');
if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-directory-alt "Select Directory Alt" ""');
if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-color ' + string(c_red));
shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

str = shellscript_evaluate(prefix + '--get-color-ext ' + string(c_red) + ' "Colors Ext"');
shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');

game_end();
For GameMaker Studio 2.x users:
Code:
zip_unzip(working_directory + "dlgmod64.zip", game_save_id + "dlgmod64/");
shellscript_evaluate(@'chmod 777 "' + game_save_id + @'dlgmod64/dlgmod64.app/Contents/MacOS/dlgmod64"');
var prefix = @'"' + game_save_id + @'dlgmod64/dlgmod64.app/Contents/MacOS/dlgmod64" ', str;

str = shellscript_evaluate(prefix + @'--show-message "Hello World" "DialogModule"');
shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--show-message-cancelable "Hello World" "DialogModule"');
shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--show-question "Yes or no?" "DialogModule"');
shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--show-question-cancelable "Yes, no, or cancel?" "DialogModule"');
shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--show-attempt "Hello World!" "Error - DialogModule"');
shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--show-error "Hello World!" 0 "Error - DialogModule"');
if (str == "") { game_end(); exit; } shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-string "Enter a string:" "Hello World!" "DialogModule"');
if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-password "Enter a string password:" "Hello World!" "DialogModule"');
if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-integer "Enter an integer:" 0 "DialogModule"');
shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-passcode "Enter an integer passcode:" 0 "DialogModule"');
shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

var filter = "Sprite Images (*.png *.gif *.jpg *.jpeg)|*.png;*.gif;*.jpg;*.jpeg|Background Images (*.png)|*.png|All Files (*.*)|*.*";

str = shellscript_evaluate(prefix + @'--get-open-filename "' + filter + @'" "Select a File"');
if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-open-filename-ext "' + filter + @'" "Select a File" "" "Open Ext"');
if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-open-filenames "' + filter + @'" "Select Files"');
if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-open-filenames-ext "' + filter + @'" "Select Files" "" "Open Ext"');
if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-save-filename "' + filter + @'" "Untitled.png"');
if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-save-filename-ext "' + filter + @'" "Untitled.png" "" "Save As Ext"');
if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-directory ""');
if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-directory-alt "Select Directory Alt" ""');
if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-color ' + string(c_red));
shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

str = shellscript_evaluate(prefix + @'--get-color-ext ' + string(c_red) + @' "Colors Ext"');
shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');

game_end();
Then you will need to archive into a zip file the "dlgmod64.app", (make sure it's in the root directory of the zip), and import that zip as an included file, naming it "dlgmod64.zip". Now you are ready to test run your game!

You may also build and distribute the app as it is, but before you do so, you will need to code-sign the "dlgmod64.app" mac app yourself. There are many tutorials online that will show you how to run mac apps that are not code-signed that were downloaded from the internet. Google is your friend for that step.

To reiterate, once you have enabled the app to run, and then code-signed it, you may then use it in your GameMaker-created mac apps.

Should you change the GML to do something else, of which, you may actually need for a final game, you can follow all these steps with that extension and/or "dlgmod64.zip" file, as needed, edited to your liking, then you may code-sign your app for distribution.
 
Last edited:

Tsa05

Member
Here's a Windows Batch script that is *technically* utterly useless, but since that's not the point of this topic, I'm forced to reveal that I find it useful to convince myself that my life is going somewhere:

countLines.bat (Counts lines of gml in a project folder and offers you comfort)
You place it in the project directory of a game (where the .yyp file is located) and run it from a command prompt.
Code:
@echo off

:CountLines
setlocal
set /a totalNumLines = 0
for /r %1 %%F in (*.gml) do (
  for /f %%N in ('find /v /c "" ^<"%%F"') do set /a totalNumLines+=%%N
)

echo Total number of code lines = %totalNumLines%
echo Making progress!
 

Samuel Venable

Time Killer
I have updated the tutorial in post #9 to be much simpler by removing the need for AppleScript and instead just executing the shellscripts from the extension directly, allowing it all to be done in GML.
 

EvanSki

King of Raccoons
So I have a Console that can read keywords and activate code based on those keywords
I built this ontop of @GMWolf 's text input system
so any of his code that i did not edit will not be shown
Right now this is still in testing phases and has place holders as command values but you should be able to tweak it and use it as you wish

forum.yoyogames/index.php?threads/evans-console-project.66484
check out this instead

This is the meat and potatoes of everything, this gets what the player types and puts the individual keys into the string and then when the player presses enter starts up the scripts

Code:
//so this amazing line of code gets the UTF8 index code of the keyboard key you pressed and adds it to the string if the key that you pressed is an allowed key
if (string_count(chr(keyboard_key), enabled_keys)) && (string_length(chr(keyboard_key)) == 1)
{
        switch(keyboard_lastchar)
        {
            //case "//": text += "/" break;
            default: text += keyboard_lastchar; break;
        //all this here fixes how game maker does strings
        }
}
else
{
    //this passes all of the information into variables we then shoot into a script
    pressed_key = keyboard_key;
    lc_key = keyboard_lastchar;
    script_execute(scr_key_list);
    text += (special_key);
    special_key = "";
}

//all of this is for keys that are control sensative
if !keyboard_check(vk_control)
{
    switch(keyboard_key)
    {
        case vk_enter: script_execute(scr_read_input); break;
        case vk_backspace:{
                              switch(string_char_at(text, (string_length(text) - 1) ) )
                              {
                                  //case "\\": text = string_copy(text,0,(string_length(text) - 2) ) break;
                                  default  : text = string_copy(text,0,(string_length(text) - 1) ) break;
                              }
                          }break;
        default: break;
    }
}
else
{
    ///these are the shortcut keys
    switch(keyboard_key)
    {
        case ( ord("V") ) : text += clipboard_get_text(); break;
        case vk_backspace : text = string_copy(text,0,(string_length(text) - string_length(text)) ) break;
        default: break;
    }
}

This is an extension of GMwolfs Enabled keys list, this allows us to use every and any key on the keyboard, using UTF-8, then passes those to the input system and the filtering systems

Code:
special_key = ""; // making an empty var that were going to put our key into
//This Region is just a comment to remember what number is what
#region //set keys ---------------------------
/*  list:
    (220 = \)
    (219 = [ {)
    (221 = ] })
    (190 = .)
    (191 = / ?)
    (187 = = +)
    (189 = - _)
    (188 = ,)
    (192 = ` ~) if using this as a debug system I should add an if enabled feature to this for bringing the console up or down
    (96  = numpad_0)
    (97  = numpad_1)
    (98  = numpad_2)
    (99  = numpad_3)
    (100 = numpad_4)
    (101 = numpad_5)
    (102 = numpad_6)
    (103 = numpad_7)
    (104 = numpad_8)
    (105 = numpad_9)
    (106 = numpad_*)
    (107 = numpad_+)
    (109 = numpad_-)
    (110 = numpad_.)
    (111 = numpad_/)
    (13  = numpad_enter)
    (33  = pg_up)
    (34  = pg_down)
    (222 = " ')
*/
#endregion
//This Region is just a comment to remember what number is what

//This region sets variables as the UTF-8/ASCII code
#region //key id list
enum k_id{
    backslash = 220,
    bracksL   = 219,
    bracksR   = 221,
    ddot      = 190,
    foreslash = 191,
    eqplus      = 187,
    minline      = 189,
    comma      = 188,
    tilda     = 192,
    np_0      =  96,
    np_1      =  97,
    np_2      =  98,
    np_3      =  99,
    np_4      = 100,
    np_5      = 101,
    np_6      = 102,
    np_7      = 103,
    np_8      = 104,
    np_9      = 105,
    np_star      = 106,
    np_plus   = 107,
    np_min    = 109,
    np_ddot   = 110,
    np_fslash = 111,
    np_enter  =  13,
    pg_up     =  33,
    pg_down   =  34,
    printhes   =  222,

}
#endregion

#region //If key press do this
switch(pressed_key)
{
    case (k_id.backslash)       : special_key = "\\";       break;
    case (k_id.bracksL)           : special_key = (lc_key);   break;
    case (k_id.bracksR)           : special_key = (lc_key);   break;
    case (k_id.ddot)           : special_key = ".";           break;
    case (k_id.foreslash)       : special_key = (lc_key);   break;
    case (k_id.eqplus)           : special_key = (lc_key);   break;
    case (k_id.minline)           : special_key = (lc_key);   break;
    case (k_id.comma)           : special_key = ",";           break;
  //case (k_id.tilda)           : if enabled                   break;
    case (k_id.np_0)           : special_key = "0";           break;
    case (k_id.np_1)           : special_key = "1";           break;
    case (k_id.np_2)           : special_key = "2";           break;
    case (k_id.np_3)           : special_key = "3";           break;
    case (k_id.np_4)           : special_key = "4";           break;
    case (k_id.np_5)           : special_key = "5";           break;
    case (k_id.np_6)           : special_key = "6";           break;
    case (k_id.np_7)           : special_key = "7";           break;
    case (k_id.np_8)           : special_key = "8";           break;
    case (k_id.np_9)           : special_key = "9";           break;
    case (k_id.np_star)           : special_key = "*";           break;
    case (k_id.np_plus)           : special_key = "+";           break;
    case (k_id.np_min)           : special_key = "-";           break;
    case (k_id.np_ddot)           : special_key = ".";           break;
    case (k_id.np_fslash)       : special_key = "/";           break;
    case (k_id.np_enter)       : special_key = ""; script_execute(scr_read_input);           break;
    case (k_id.pg_up)           :            break;
    case (k_id.pg_down)           :            break;
    case (k_id.printhes)       : special_key = (lc_key);   break;

    default: special_key = "";
}
#endregion

return(special_key)

This "Reads" what the player has typed, makes it all upper case(tho this could also be set to lower they just all need to be the same case) then shoots it to the filtering scripts

Code:
//script that reads what the player has typed into the console
con_input = false;
input_string = text; //get the string
text = "";
input_string = string_upper(input_string); // makes string capital

script_execute(scr_cmd_list); //the list of avaliable commands

This is like the key list but for commands we want the player to use, this looks through a grid thats created and finds which commands are set as true being the ones typed in the string from before

Code:
cmd_list_size = 10; //max size of grid
cmd_list = ds_grid_create(2,cmd_list_size); //create grid
ds_grid_clear(cmd_list,0);

var grid = cmd_list;

//set all commands to false
for (var i = 0; i < cmd_list_size; i++)
{
    ds_grid_add(grid, 1, i, false);
}

//COMMANDS                   
grid[# 0, 0] = "LOGOFF";
grid[# 0, 1] = "HELLO";
grid[# 0, 2] = "PH";
grid[# 0, 3] = "PH";
grid[# 0, 4] = "PH";
grid[# 0, 5] = "PH";
grid[# 0, 6] = "PH";
grid[# 0, 7] = "PH";
grid[# 0, 8] = "PH";
grid[# 0, 9] = "PH";




script_execute(scr_filter_input); //find the commands in our input

This is what actually tells the game if the players string contains any commands or not at all, it has to protections if the player has more then one command in a string or none at all

Code:
var grid = cmd_list;
var input = input_string;


//read the string and find out which ones should be true
for (var i = 0; i < (cmd_list_size); i++)
{
    var section = ds_grid_get(grid, 0, i);
    if (string_pos(section,input))
    {
        grid[# 1, i] = true;
    }
}



//find out if more then one are true or just one is true
for (var Cell = 0, i = 0; i < (cmd_list_size); i++)
{
    Cell = ( (Cell) + ( ds_grid_get(grid, 1, i) ) );
}

switch(Cell)
{
    case 1: cmd = 1;                break;
    case 2: cmd = ("CMD_OVERFLOW"); break;

    default: cmd = ("CMD_ERROR"); break;
}

Command = cmd;
script_execute(scr_cmd_input); //do the commands

This is where the magic happens, this does the command in the string, so if the player typed END_GAME we could end the game. and returns this to the very beginning of our script line

Code:
switch(Command) //do we have a command or error
{
    case ("CMD_OVERFLOW") : show_message("To many commands in one line") break;
    case ("CMD_ERROR")    : show_message("'"+input_string+"'"+"is not recognized as an internal or external command or operable program.") break;
    case (1)              : show_message(1)/*Should have script on picking out which command we did and what do do with it*/ break;
}


//destroy grid
ds_grid_destroy(cmd_list);
con_input = true;
 
Last edited:

Samuel Venable

Time Killer
This is the Windows version of the code I have in the OP.
Code:
/*

 MIT License

 Copyright © 2019 Samuel Venable

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

*/

#include <windows.h>
#include <cwchar>
#include <string>
#include <vector>

using std::basic_string;
using std::wstring;
using std::string;
using std::vector;
using std::size_t;

#define EXPORTED_FUNCTION extern "C" _declspec(dllexport)

static inline wstring widen(string str) {
  const size_t wchar_count = str.size() + 1;
  vector<wchar_t> buf(wchar_count);
  return wstring{ buf.data(), (size_t)MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buf.data(), (int)wchar_count) };
}

static inline string narrow(wstring str) {
  int nbytes = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.length(), NULL, 0, NULL, NULL);
  vector<char> buf((size_t)nbytes);
  return string{ buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.length(), buf.data(), nbytes, NULL, NULL) };
}

static inline char *evaluate_shell_helper(char *command) {
  string str_command = command;
  wstring wstr_command = widen(str_command);
  wchar_t cwstr_command[32768];
  wcsncpy_s(cwstr_command, wstr_command.c_str(), 32768);
  BOOL ok = TRUE;
  HANDLE hStdInPipeRead = NULL;
  HANDLE hStdInPipeWrite = NULL;
  HANDLE hStdOutPipeRead = NULL;
  HANDLE hStdOutPipeWrite = NULL;
  SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
  ok = CreatePipe(&hStdInPipeRead, &hStdInPipeWrite, &sa, 0);
  if (ok == FALSE) return (char *)"";
  ok = CreatePipe(&hStdOutPipeRead, &hStdOutPipeWrite, &sa, 0);
  if (ok == FALSE) return (char *)"";
  STARTUPINFOW si = { };
  si.cb = sizeof(STARTUPINFOW);
  si.dwFlags = STARTF_USESTDHANDLES;
  si.hStdError = hStdOutPipeWrite;
  si.hStdOutput = hStdOutPipeWrite;
  si.hStdInput = hStdInPipeRead;
  PROCESS_INFORMATION pi = { };
  if (CreateProcessW(NULL, cwstr_command, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
    while (WaitForSingleObject(pi.hProcess, 5) == WAIT_TIMEOUT) {
      MSG msg;
      if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }
    CloseHandle(hStdOutPipeWrite);
    CloseHandle(hStdInPipeRead);
    char buf[4096] = { };
    DWORD dwRead = 0;
    DWORD dwAvail = 0;
    ok = ReadFile(hStdOutPipeRead, buf, 4095, &dwRead, NULL);
    string str_buf = buf; wstring output = widen(str_buf);
    while (ok == TRUE) {
      buf[dwRead] = '\0';
      OutputDebugStringW(output.c_str());
      _putws(output.c_str());
      ok = ReadFile(hStdOutPipeRead, buf, 4095, &dwRead, NULL);
      str_buf = buf; output += widen(str_buf);
    }
    CloseHandle(hStdOutPipeRead);
    CloseHandle(hStdInPipeWrite);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    static string str_output;
    str_output = narrow(output);
    return (char *)str_output.c_str();
  }
  return (char *)"";
}

EXPORTED_FUNCTION char *evaluate_shell(char *command) {
  char *result = evaluate_shell_helper(command);
  size_t len = strlen(result);
  if (len >= 2) {
    if (result[len - 2] == '\r') {
      if (result[len - 1] == '\n') {
        result[len - 2] = '\0';
      }
    }
  }
  return result;
}
 
Last edited:

EvanSki

King of Raccoons
So I have a Console that can read keywords and activate code based on those keywords
I built this ontop of @GMWolf 's text input system so any of his code that i did not edit will not be shown
Right now this is still in testing phases and has place holders as command values but you should be able to tweak it and use it as you wish
//forum.yoyogames/index.php?threads/evans-console-project.66484
as of current:
it can only read and execute 1 word commands with no arguments so (shutdown -i)
wont work

currently working on this thankfully Ive left some room in my code where i can just plug it in and have it be all sorted out in the command script


Code:
if global.con_input {

    blink = true;
    alarm[0] = blink_speed;

    //show_message(keyboard_key);


    //keyboard_key gives a keycode, chr turns the code into a letter, then we count how many of that letter is in enabled_keys, then it makes sure that key is only one key being pressed
    if (string_count(chr(keyboard_key), enabled_keys)) && (string_length(chr(keyboard_key)) == 1)
    {
            switch(keyboard_lastchar)
            {
                //case "//": text += "/" break;
                default: text += keyboard_lastchar; break;
            }
    }
    else
    {
        pressed_key = keyboard_key;
        lc_key = keyboard_lastchar;
        script_execute(scr_key_list);
        text += (special_key);
        special_key = "";
    }


    if !keyboard_check(vk_control)
    {
        switch(keyboard_key)
        {
            case vk_enter: script_execute(scr_read_input);  break;
            case vk_backspace:{
                                  switch(string_char_at(text, (string_length(text) - 1) ) )
                                  {
                                      //case "\\": text = string_copy(text,0,(string_length(text) - 2) ) break;
                                      default  : text = string_copy(text,0,(string_length(text) - 1) ) break;
                                  }
                              }break;
            default: break;
        }
    }
    else
    {
        switch(keyboard_key)
        {
            case ( ord("V") ) : text += clipboard_get_text(); break;
            case vk_backspace : text = string_copy(text,0,(string_length(text) - string_length(text)) ) break;
            default: break;
        }
    }

}

Code:
special_key = ""; // making an empty var that were going to put our key into
#region //set keys ---------------------------
/*  list:
    (220 = \)
    (219 = [ {)
    (221 = ] })
    (190 = .)
    (191 = / ?)
    (187 = = +)
    (189 = - _)
    (188 = ,)
    (192 = ` ~) if using this as a debug system I should add an if enabled feature to this for bringing the console up or down
    (96  = numpad_0)
    (97  = numpad_1)
    (98  = numpad_2)
    (99  = numpad_3)
    (100 = numpad_4)
    (101 = numpad_5)
    (102 = numpad_6)
    (103 = numpad_7)
    (104 = numpad_8)
    (105 = numpad_9)
    (106 = numpad_*)
    (107 = numpad_+)
    (109 = numpad_-)
    (110 = numpad_.)
    (111 = numpad_/)
    (13  = numpad_enter)
    (33  = pg_up)
    (34  = pg_down)
    (222 = " ')
*/
#endregion

#region //key id list
enum k_id{
    backslash = 220,
    bracksL   = 219,
    bracksR   = 221,
    ddot      = 190,
    foreslash = 191,
    eqplus      = 187,
    minline      = 189,
    comma      = 188,
    tilda     = 192,
    np_0      =  96,
    np_1      =  97,
    np_2      =  98,
    np_3      =  99,
    np_4      = 100,
    np_5      = 101,
    np_6      = 102,
    np_7      = 103,
    np_8      = 104,
    np_9      = 105,
    np_star      = 106,
    np_plus   = 107,
    np_min    = 109,
    np_ddot   = 110,
    np_fslash = 111,
    np_enter  =  13,
    pg_up     =  33,
    pg_down   =  34,
    printhes   =  222,

}
#endregion

#region //If key press do this
switch(pressed_key)
{
    case (k_id.backslash)       : special_key = "\\";       break;
    case (k_id.bracksL)           : special_key = (lc_key);   break;
    case (k_id.bracksR)           : special_key = (lc_key);   break;
    case (k_id.ddot)           : special_key = ".";           break;
    case (k_id.foreslash)       : special_key = (lc_key);   break;
    case (k_id.eqplus)           : special_key = (lc_key);   break;
    case (k_id.minline)           : special_key = (lc_key);   break;
    case (k_id.comma)           : special_key = ",";           break;
  //case (k_id.tilda)           : if enabled                   break;
    case (k_id.np_0)           : special_key = "0";           break;
    case (k_id.np_1)           : special_key = "1";           break;
    case (k_id.np_2)           : special_key = "2";           break;
    case (k_id.np_3)           : special_key = "3";           break;
    case (k_id.np_4)           : special_key = "4";           break;
    case (k_id.np_5)           : special_key = "5";           break;
    case (k_id.np_6)           : special_key = "6";           break;
    case (k_id.np_7)           : special_key = "7";           break;
    case (k_id.np_8)           : special_key = "8";           break;
    case (k_id.np_9)           : special_key = "9";           break;
    case (k_id.np_star)           : special_key = "*";           break;
    case (k_id.np_plus)           : special_key = "+";           break;
    case (k_id.np_min)           : special_key = "-";           break;
    case (k_id.np_ddot)           : special_key = ".";           break;
    case (k_id.np_fslash)       : special_key = "/";           break;
    case (k_id.np_enter)       : special_key = ""; script_execute(scr_read_input);           break;
    case (k_id.pg_up)           :            break;
    case (k_id.pg_down)           :            break;
    case (k_id.printhes)       : special_key = (lc_key);   break;

    default: special_key = "";
}
#endregion

return(special_key)

Code:
//script that reads what the player has typed into the console
global.con_input = false;

//LITTERLY THE LIFE BLOOD OF ALL THIS
if (text = "")
{
    global.con_input = true;
    exit;
}

input_string = text; //get the string
text = "";
input_string = string_upper(input_string); // makes string capital

script_execute(scr_con_list); //the list of avaliable commands

This is the part you edit for what you want typed in to activate your command this will also set what your script name will be
Code:
cmd_list_size = 10; //max size of grid
cmd_list = ds_grid_create(2,cmd_list_size); //create grid
ds_grid_clear(cmd_list,0);

var grid = cmd_list;

//set all commands to false
for (var i = 0; i < cmd_list_size; i++)
{
    ds_grid_add(grid, 1, i, false);
}

//COMMANDS                   
grid[# 0, 0] = "LOGOFF";
grid[# 0, 1] = "HELLO";
grid[# 0, 2] = "PH";
grid[# 0, 3] = "PH";
grid[# 0, 4] = "PH";
grid[# 0, 5] = "PH";
grid[# 0, 6] = "PH";
grid[# 0, 7] = "PH";
grid[# 0, 8] = "PH";
grid[# 0, 9] = "PH";




script_execute(scr_filter_input); //find the commands in our input

Code:
var grid = cmd_list;
var input = input_string;


//read the string and find out which ones should be true
for (var i = 0; i < (cmd_list_size); i++)
{
    var section = ds_grid_get(grid, 0, i);
    if (string_pos(section,input))
    {
        grid[# 1, i] = true;
    }
}



//find out if more then one are true or just one is true
for (var Cell = 0, i = 0; i < (cmd_list_size); i++)
{
    Cell = ( (Cell) + ( ds_grid_get(grid, 1, i) ) );
}

switch(Cell)
{
    case 0: cmd = ("CMD_ERROR");    break;
    case 1: cmd = 1;                break;
    case 2: cmd = ("CMD_OVERFLOW"); break;
}

Command = cmd;
script_execute(scr_con_input); //do the commands

Code:
switch(Command) //do we have a command or error
{
    case ("CMD_OVERFLOW") : show_debug_message("To many commands in one line") break;
    case ("CMD_ERROR")    : show_debug_message("'"+input_string+"'"+"is not recognized as an internal or external command or operable program.") break;
    case (1)              : script_execute(scr_do_con);
}

/*
//destroy grid
ds_grid_destroy(cmd_list);
global.con_input = true;
*/

Code:
var grid = cmd_list;

//loop through grid looking for which command is 1
for (var i = 0; i < cmd_list_size; i++)
{
    grid_place = (grid[# 1, i]);
    if (grid_place == true)
    {
        //save what command it is
        value = ds_grid_get(grid,0,i);
        //show_message(string(value));
    }
}

//get the asset name of the command, lower case value because case sensitive
value = string_lower(value);
var command = asset_get_index("scr_cmd_"+value);

//if it exists do it else panic
if (command != -1)
{
    script_execute(command);
}else{
    show_message(value);
    show_message(command);
    ds_grid_destroy(cmd_list);
    game_end();
}
Okay so above should activate any command givin its named (scr_cmd_"Name of command here")

Code:
//What you want the command to do ---------------------
show_message("aneurysm coding");


//Clean up---------------------------------------------------------
with(obj_input)
{
    //destroy grid
    ds_grid_destroy(cmd_list);
    global.con_input = true; 
}
 
Last edited:

Lonewolff

Member
Starting off here with a simple but often over complicated matter.

Running your game locked at any refresh rate. Do with this as you will. :)

Code:
// Create event

room_speed = 9999;
display_reset(0, true);

Code:
// Step event

var dt = detla_time / 1000000;
var move_speed = 100;         // 100 pixels per second

x += move_speed * dt;

Simple and to the point. The object moves at exactly 100 pixels per second on any monitor, regardless of refresh rate.

Users of 60 hz monitors get 60 FPS, users of 200 hz monitors get the benefit of ultra smooth 200 FPS. No tearing. Everyone's a winner! :D
 
Starting off here with a simple but often over complicated matter.

Running your game locked at any refresh rate. Do with this as you will. :)

Code:
// Create event

room_speed = 9999;
display_reset(0, true);

Code:
// Step event

var dt = detla_time / 1000000;
var move_speed = 100;         // 100 pixels per second

x += move_speed * dt;

Simple and to the point. The object moves at exactly 100 pixels per second on any monitor, regardless of refresh rate.

Users of 60 hz monitors get 60 FPS, users of 200 hz monitors get the benefit of ultra smooth 200 FPS. No tearing. Everyone's a winner! :D
Good code, but for a lot of things this is - in fact - too simple. Take acceleration, for example. x += acceleration*dT won't cut it. Physics would be totally different depending on framerate. You'd have to instead do something more along the lines of
Code:
x += (velocityX + velocityX + acceleration * dT) * dT * 1/2;
To get accurate physics across all framerates.
 

Lonewolff

Member
@nacho_chicken Absolutely right. My example was the absolute bare bones on how easy it is to run at the monitors VSync.

You'd obviously have to take delta time into account for any movement calculations. Best done at the beginning of a new project. Not so fun integrating it in to an existing project, unless you have all of your code extremely well laid out and planned.
 

Samuel Venable

Time Killer
It's a code snippet thread, son. ;)

The initial post itself could qualify for an entire forum category.
Keep in mind the original post as a guideline, before I gave an example to start us off. Any side questions or discussions are better put in private messages and if anyone has information you find needs critiquing, then private message them, and then they can edit accordingly.

Edit:

Looks like I broke my own rule just now, silly me.
 

Samuel Venable

Time Killer
Borderless Toggle Extension (Linux X11 Edition).

If and when GameMaker Studio 2 switches to Wayland, this, I am pretty sure, will no longer work, and will need a re-write.

ShowBorder.h (C++ Header):
Code:
/*

 MIT License

 Copyright © 2019 Samuel Venable

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

*/

#ifdef _WIN32
#define EXPORTED_FUNCTION extern "C" __declspec(dllexport)
#else /* macOS and Linux */
#define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))
#endif

EXPORTED_FUNCTION double window_get_showborder(void *hwnd);
EXPORTED_FUNCTION double window_set_showborder(void *hwnd, double show, double clientx, double clienty);
ShowBorder.cpp (C++ Source):
Code:
/*

 MIT License

 Copyright © 2019 Samuel Venable

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

*/

#include "ShowBorder.h"
#include <X11/Xlib.h>
#include <climits>

typedef struct {
  unsigned long flags;
  unsigned long functions;
  unsigned long decorations;
  long inputMode;
  unsigned long status;
} Hints;

double window_get_showborder(void *hwnd) {
  Atom type;
  int format;
  unsigned long bytes;
  unsigned long items;
  unsigned char *data = NULL;
  bool ret = true;
  Display *d = XOpenDisplay(NULL);
  Window w = (Window)hwnd;
  Atom property = XInternAtom(d, "_MOTIF_WM_HINTS", False);
  if (XGetWindowProperty(d, w, property, 0, LONG_MAX, False, AnyPropertyType, &type, &format, &items, &bytes, &data) == Success && data != NULL) {
    Hints *hints = (Hints *)data;
    ret = hints->decorations;
    XFree(data);
  }
  XCloseDisplay(d);
  return ret;
}

double window_set_showborder(void *hwnd, double show, double clientx, double clienty) {
  Display *d = XOpenDisplay(NULL);
  Window w = (Window)hwnd;
  Atom aKWinRunning = XInternAtom(d, "KWIN_RUNNING", True);
  bool bKWinRunning = (aKWinRunning != None);
  XWindowAttributes wa;
  Window root, parent, *child; uint children;
  XWindowAttributes pwa;
  for (;;) {
    XGetWindowAttributes(d, w, &wa);
    XQueryTree(d, w, &root, &parent, &child, &children);
    XGetWindowAttributes(d, parent, &pwa);
    if ((bKWinRunning ? pwa.x : wa.x) || (bKWinRunning ? pwa.y : wa.y) || !window_get_showborder(hwnd))
      break;
  }
  static const int xoffset = bKWinRunning ? pwa.x : wa.x;
  static const int yoffset = bKWinRunning ? pwa.y : wa.y;
  Hints hints;
  Atom property = XInternAtom(d, "_MOTIF_WM_HINTS", False);
  hints.flags = 2;
  hints.decorations = show;
  XChangeProperty(d, w, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
  int xpos = show ? clientx - xoffset : clientx;
  int ypos = show ? clienty - yoffset : clienty;
  XMoveResizeWindow(d, w, xpos, ypos, bKWinRunning ? wa.width : wa.width + xoffset, bKWinRunning ? wa.height : wa.height + yoffset);
  XCloseDisplay(d);
  return 0;
}
ShowBorder.sh (Build Script):
Code:
cd "${0%/*}"
g++ -c -std=c++17 "ShowBorder.cpp" -fPIC -m64
g++ "ShowBorder.o" -o "ShowBorder.so" -shared -fPIC -m64
GameMaker Studio 2 Usage:
Code:
/// @desc Global Mouse Left Pressed Event:
var hwnd = window_handle(); // Get GameMaker's Runner Window ID
var show = !window_get_showborder(hwnd); // Get Destination Border State
var clientx = window_get_x(); // Get the Current Window X Position
var clienty = window_get_y(); // Get the Current Window Y Position
window_set_showborder(hwnd, show, clientx, clienty); // Set Border State
GameMaker Studo 2 Result:

https://i.imgur.com/CTV3A6b.gif (KDE neon 5.16.4)
https://i.imgur.com/rwpD476.gif (Lubuntu 18.04.2 LTS)
https://i.imgur.com/eB4OUfk.gif (Xubuntu 18.04.2 LTS)
https://i.imgur.com/REAFb9B.gif (Ubuntu 18.04.2 LTS)

Needless to say, it also worked in Kubuntu 18.04.2 LTS, but I don't have screenshots for that.
 
Last edited:

Samuel Venable

Time Killer
To expand on my previous post, KDE's (and LXQt's) Window Manager, KWin, has a lot of oddities that make it behave and react different to code that separate it from the consistent behaviors of most other Window Managers, including GNOME, XFCE, and LXDE. One of these oddities, as demonstrated in my previous post, getting a window's client area x/y position relative to the titlebar's x/y position, requires the use of XQueryTree, but, it will need to be used differently depending on whether running under KWin or some other Window Manager. This requires checking in code whether KWin is the current Window Manager running, and to do this, you may use the following code:
Code:
#include <X11/Xlib.h>
// simple helper function to determine whether kwin is running.
// do not call this from GM without changing the return to double
// and exporting the function properly so that it will be recognized.
bool kwin_running() {
  Display *d = XOpenDisplay(NULL);
  // don't change the last argument to false or bad stuff will happen.
  Atom aKWinRunning = XInternAtom(d, "KWIN_RUNNING", True);
  bool bKWinRunning = (aKWinRunning != None);
  XCloseDisplay(d);
  return bKWinRunning;
}
The above code could also be used in combination with my Dialog Module extension, so that if (kwin_running() == true), use KDE widgets with KDialog, otherwise use GTK+ widgets with Zenity, if you were to export the above function to a library with the double return type. As I just explained, you could then do this, for example:
Code:
if (kwin_running() == true)
  wigdet_set_system("KDialog");
else
  wigdet_set_system("Zenity");
Special thanks to the KDE community forum member, kde-cfeck, for helping me out with this: https://forum.kde.org/viewtopic.php?f=305&t=161880
 
Last edited:

rytan451

Member
Code:
#macro BEGIN_ASSERT if (debug_mode || os_get_config() == "debug") && !(
#macro END_ASSERT ) show_error("Assertion failed", true)

// Example:

BEGIN_ASSERT something() END_ASSERT
Two macros together create no-cost assertion. (debug_mode || os_get_config() == "debug") is a compile-time constant, and because of short-circuiting, something() will only ever be called if it is true. If I understand compilers correctly, the entire example code would be removed, if not by the preprocessor, then by some later step.
 
Last edited:

rytan451

Member
os_get_config() is a well-defined value at compile-time. I don't see any reason why it wouldn't be a compile-time constant. After all, gml_pragma looks like a function but it's really just a compiler flag.

With the configuration options in macros:

Code:
#macro BEGIN_ASSERT if false && (
#macro BEGIN_ASSERT:debug if true && !(
#macro END_ASSERT ) show_error("Assertion failed", true)
 

Yal

🐧 *penguin noises*
GMC Elder
Here's some convenience functions I use a lot:

Array: a script that creates an array from arguments and returns it. Now you can pretend arrays are first-order citizens in GM!
Code:
///array(val1,val2,...)
var a, c;
for(c = argument_count-1;c >= 0;c--){
    a[c] = argument[c];
}
return a;

Trivial String Print Format: basically printf without any of the actual formatting options. Useful for debug messages and string construction nonetheless, though!
Code:
///tsprintf(format,...)
//Trivial String Print Format
//Replaces each % percent sign with an argument, then returns the string.
//Inteded to make writing debug printouts less painful.
var c;
for(c = 1;c < argument_count;c++){
    argument[0] = string_replace(argument[0],"%",string(argument[c]));
}
return argument[0];
 

Fanatrick

Member
Simple color gradient function:
Code:
/// @desc color_gradient(color_array, progress);
/// @param color_array
/// @param progress
/***************************************************
  HowToUse
    Interpolate through a gradient defined by
  an array of colors and percentile progress
  value;
 
  returns - color value
***************************************************/
var _arr = argument0,
    _max = array_length_1d(_arr)-1
    _prog = (argument1 mod 1) * _max;

return merge_color(_arr[floor(_prog)], _arr[ceil(_prog)], frac(_prog));
Ex:
Code:
var _grad = [c_red, c_green, c_blue],
    _val = 0.5 + sin(current_time * 0.001) * 0.5,
    _col = color_gradient(_grad, _val);
 

Samuel Venable

Time Killer
This is the Windows version of the code I have in the OP.
Code:
/*

 MIT License

 Copyright © 2019 Samuel Venable

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

*/

#include <windows.h>
#include <cwchar>
#include <string>
#include <vector>

using std::basic_string;
using std::wstring;
using std::string;
using std::vector;
using std::size_t;

#define EXPORTED_FUNCTION extern "C" _declspec(dllexport)

static inline wstring widen(string str) {
  const size_t wchar_count = str.size() + 1;
  vector<wchar_t> buf(wchar_count);
  return wstring{ buf.data(), (size_t)MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buf.data(), (int)wchar_count) };
}

static inline string narrow(wstring str) {
  int nbytes = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.length(), NULL, 0, NULL, NULL);
  vector<char> buf((size_t)nbytes);
  return string{ buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.length(), buf.data(), nbytes, NULL, NULL) };
}

static inline char *evaluate_shell_helper(char *command) {
  string str_command = command;
  wstring wstr_command = widen(str_command);
  wchar_t cwstr_command[32768];
  wcsncpy_s(cwstr_command, wstr_command.c_str(), 32768);
  BOOL ok = TRUE;
  HANDLE hStdInPipeRead = NULL;
  HANDLE hStdInPipeWrite = NULL;
  HANDLE hStdOutPipeRead = NULL;
  HANDLE hStdOutPipeWrite = NULL;
  SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
  ok = CreatePipe(&hStdInPipeRead, &hStdInPipeWrite, &sa, 0);
  if (ok == FALSE) return (char *)"";
  ok = CreatePipe(&hStdOutPipeRead, &hStdOutPipeWrite, &sa, 0);
  if (ok == FALSE) return (char *)"";
  STARTUPINFOW si = { };
  si.cb = sizeof(STARTUPINFOW);
  si.dwFlags = STARTF_USESTDHANDLES;
  si.hStdError = hStdOutPipeWrite;
  si.hStdOutput = hStdOutPipeWrite;
  si.hStdInput = hStdInPipeRead;
  PROCESS_INFORMATION pi = { };
  if (CreateProcessW(NULL, cwstr_command, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
    while (WaitForSingleObject(pi.hProcess, 5) == WAIT_TIMEOUT) {
      MSG msg;
      if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }
    CloseHandle(hStdOutPipeWrite);
    CloseHandle(hStdInPipeRead);
    char buf[4096] = { };
    DWORD dwRead = 0;
    DWORD dwAvail = 0;
    ok = ReadFile(hStdOutPipeRead, buf, 4095, &dwRead, NULL);
    string str_buf = buf; wstring output = widen(str_buf);
    while (ok == TRUE) {
      buf[dwRead] = '\0';
      OutputDebugStringW(output.c_str());
      _putws(output.c_str());
      ok = ReadFile(hStdOutPipeRead, buf, 4095, &dwRead, NULL);
      str_buf = buf; output += widen(str_buf);
    }
    CloseHandle(hStdOutPipeRead);
    CloseHandle(hStdInPipeWrite);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    static string str_output;
    str_output = narrow(output);
    return (char *)str_output.c_str();
  }
  return (char *)"";
}

EXPORTED_FUNCTION char *evaluate_shell(char *command) {
  char *result = evaluate_shell_helper(command);
  size_t len = strlen(result);
  if (len >= 2) {
    if (result[len - 2] == '\r') {
      if (result[len - 1] == '\n') {
        result[len - 2] = '\0';
      }
    }
  }
  return result;
}
I updated this post to create an actual pipe, (rather than redirecting output to a temporary text file for automatic reading), which is for technical reasons a better practice for this use case, but sacrifices UTF-8 support unfortunately, as I have yet to figure out how to achieve that with this particular method.
 
Last edited:

Samuel Venable

Time Killer
Some Mac OS X and Objective C programming. I am actually using this for a command line application I am writing in combination with GameMaker Studio 2 and my "Evaluate Shell" extension for Windows, Mac, and Linux. Note that donations are welcome.

This is how complicated Apple makes activating a window that belongs to an external application:
For anyone looking for an Objective C solution:
Code:
#import <Cocoa/Cocoa.h>
#import <libproc.h>
#import <string.h>
#import <stdlib.h>
#import <stdio.h>

bool activate_window_of_id(unsigned long wid) {
  bool success = false;
  const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
  CFIndex windowCount = 0;
  if ((windowCount = CFArrayGetCount(windowArray))) {
    for (CFIndex i = 0; i < windowCount; i++) {
      NSDictionary *windowInfoDictionary = (__bridge NSDictionary *)((CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i));
      NSNumber *ownerPID = (NSNumber *)(windowInfoDictionary[(id)kCGWindowOwnerPID]);
      NSNumber *level = (NSNumber *)(windowInfoDictionary[(id)kCGWindowLayer]);
      if (level.integerValue < kScreensaverWindowLevel) {
        NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
        if (wid == windowID.integerValue) {
          CFIndex appCount = [[[NSWorkspace sharedWorkspace] runningApplications] count];
          for (CFIndex j = 0; j < appCount; j++) {
            if (ownerPID.integerValue == [[[[NSWorkspace sharedWorkspace] runningApplications] objectAtIndex:j] processIdentifier]) {
              NSRunningApplication *appWithPID = [[[NSWorkspace sharedWorkspace] runningApplications] objectAtIndex:j];
              [appWithPID activateWithOptions:NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps];
              char buf[PROC_PIDPATHINFO_MAXSIZE];
              proc_pidpath(ownerPID.integerValue, buf, sizeof(buf));
              NSString *buffer = [NSString stringWithUTF8String:buf];
              unsigned long location = [buffer rangeOfString:@".app/Contents/MacOS/" options:NSBackwardsSearch].location;
              NSString *path = (location != NSNotFound) ? [buffer substringWithRange:NSMakeRange(0, location)] : buffer;
              NSString *app = [@" of application \\\"" stringByAppendingString:[path lastPathComponent]];
              NSString *index = [@"set index of window id " stringByAppendingString:[windowID stringValue]];
              NSString *execScript = [[index stringByAppendingString:app] stringByAppendingString:@"\\\" to 1"];
              char *pointer = NULL;
              size_t buffer_size = 0;
              NSMutableArray *array = [[NSMutableArray alloc] init];
              FILE *file = popen([[[@"osascript -e \"" stringByAppendingString:execScript] stringByAppendingString:@"\" 2>&1"] UTF8String], "r");
              while (getline(&pointer, &buffer_size, file) != -1)
                [array addObject:[NSString stringWithUTF8String:pointer]];
              char *error = (char *)[[array componentsJoinedByString:@""] UTF8String];
              if (strlen(error) > 0 && error[strlen(error) - 1] == '\n')
                error[strlen(error) - 1] = '\0';
              if ([[NSString stringWithUTF8String:error] isEqualToString:@""])
                success = true;
              [array release];
              free(pointer);
              pclose(file);
              break;
            }
          }
        }
      }
    }
  }
  CFRelease(windowArray);
  return success;
}
Note, unlike Daniel's answer, this will not just bring the specified application's windows to the front, it will also make sure the specific window whose id matches the one specified will be the topmost out of that app's collection of windows. It will return true on success, and false on failure. I noticed it brings to front for some apps but not for others. I'm not sure why. The code it is based on does not work as advertised for its original purpose. Although, it did help me a lot to get working all the stuff I needed to answer this question. The code my answer is based on can be found here. Ignore the original usage.
This quote was pulled from my stackoverflow answer to Activate a window using its Window ID.

Note this function I wrote only works with certain apps which you have the appropriate privileges to manipulate the properties of. That, and a Window ID is NOT the same thing as a Window Handle on Mac, unlike Windows and Linux. It will only work if you specify an existing Window ID. A Window ID can only be retrieved from an external application that is not the current app one of two ways:
  • Use an automated scripting language like AppleScript or JavaScript, which is limited to applications that have scripting enabled, or if the app happens to have a front window or be the frontmost process already.
  • You can also extract the Window ID from a Window Handle (an NSWindow *) from an app you wrote yourself and send that ID as a command line parameter to the app which needs to know that Window ID.
Due to Apple lockdown, it is impossible to get or even use an existing NSWindow * handle if it does not belong to the current app. I've actually tried passing a casted NSWindow * to and from a unsigned long long (casted that unsigned long long to and from a std::string) as a command line parameter to another app I also wrote, and while doing this with the Window ID worked, the NSWindow * did not, because an NSWindow * is only valid for the current application. To get the Window ID of an NSWindow *, provided the NSWindow * belongs to the current app, it would be as simple as doing:
Code:
unsigned long window_id = (CGWindowID)[WindowHandle windowNumber];
The rest of the work involved, as previously mentioned, to activate the window of a given Window ID, is not only overcomplicated, but also very limiting, and has quite a bit of overhead. On Windows, all of this may be done for virtually any application's Window Handle, with a single line call to one Win32 API function for each window to activate from a given Window Handle directly:
Code:
SetForegroundWindow(WindowHandle);
Needless to say, manipulating a window based on it's NSWindow * allows for a lot more options and properties you may edit, than based on a Window ID does. Again, modifying a window based on it's NSWindow * handle only works for the current app.
 
Last edited:

Mike

nobody important
GMC Elder
Here's some convenience functions I use a lot:
Array: a script that creates an array from arguments and returns it. Now you can pretend arrays are first-order citizens in GM!
Code:
///array(val1,val2,...)
var a, c;
for(c = argument_count-1;c >= 0;c--){
    a[c] = argument[c];
}
return a;
Okay... gotta ask.... why wouldn't you just do this........
Code:
var a = [val1, val2, val3....]
Why call a function to do this? You're already listing the values, so why not just use square brackets instead of a function call?
 

FrostyCat

Member
Okay... gotta ask.... why wouldn't you just do this........
Code:
var a = [val1, val2, val3....]
Why call a function to do this? You're already listing the values, so why not just use square brackets instead of a function call?
It's meant as a holdover from GMS 1.4, which didn't have the array literal syntax.
 

Samuel Venable

Time Killer
Change your Linux icon to one larger than YoYo's forced size of 64x64. All resolutions supported, even ones of a non-square aspect ratio. Requires lodepng. Can also be used to make animated window icons, if you are ok with loading/unloading separate PNG files for each individual animation frame.

Code:
/*

 MIT License

 Copyright © 2019 Samuel Venable
 Copyright © 2019 Robert B. Colton

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

*/

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include <iostream>
#include <string>

#include "lodepng.h"

static inline unsigned int nlpo2dc(unsigned int x) {
  x--;
  x |= x >> 1;
  x |= x >> 2;
  x |= x >> 4;
  x |= x >> 8;
  return x | (x >> 16);
}

void XSetIcon(Display *display, Window window, const char *icon) {
  XSynchronize(display, True);
  Atom property = XInternAtom(display, "_NET_WM_ICON", True);

  unsigned char *data = nullptr;
  unsigned pngwidth, pngheight;
  unsigned error = lodepng_decode32_file(&data, &pngwidth, &pngheight, icon);
  if (error) return;

  unsigned
   widfull = nlpo2dc(pngwidth) + 1,
   hgtfull = nlpo2dc(pngheight) + 1,
   ih, iw;

  const int bitmap_size = widfull * hgtfull * 4;
  unsigned char *bitmap = new unsigned char[bitmap_size]();

  unsigned i = 0;
  unsigned elem_numb = 2 + pngwidth * pngheight;
  unsigned long *result = new unsigned long[elem_numb]();

  result[i++] = pngwidth;
  result[i++] = pngheight;
  for (ih = 0; ih < pngheight; ih++) {
   unsigned tmp = ih * widfull * 4;
   for (iw = 0; iw < pngwidth; iw++) {
     bitmap[tmp + 0] = data[4 * pngwidth * ih + iw * 4 + 2];
     bitmap[tmp + 1] = data[4 * pngwidth * ih + iw * 4 + 1];
     bitmap[tmp + 2] = data[4 * pngwidth * ih + iw * 4 + 0];
     bitmap[tmp + 3] = data[4 * pngwidth * ih + iw * 4 + 3];
     result[i++] = bitmap[tmp + 0] | (bitmap[tmp + 1] << 8) | (bitmap[tmp + 2] << 16) | (bitmap[tmp + 3] << 24);
     tmp += 4;
   }
  }

  XChangeProperty(display, window, property, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)result, elem_numb);
  XFlush(display);
  delete[] result;
  delete[] bitmap;
  delete[] data;
}
 
Last edited:

Yal

🐧 *penguin noises*
GMC Elder
Sure... but it was posted last month... so was wondering why @Yal was still using it...
I'm still using 1.4.1773 like 99% of the time, I'm way too comfortable with this workflow to try something new :p (Essentially everything I do all the time got slower in GMS2, like writing description comments for code actions and pasting images from an external image editor into the sprite editor... getting more and more tempted to switch over now when those roadmaps items are starting to materialize, though~)
 

Mike

nobody important
GMC Elder
Yeah... I wanted to change the default "paste" action in the image editor ages ago.... but never got the time/resources to do it.
I'd still keep the brush pickup/painting though, it's incredibly useful - but move it to another key.
 

Samuel Venable

Time Killer
To expand upon the code in my previous post, here is a Linux command line application that can be used from your game maker game using the code found in the OP:

xtransientfor.cpp
Code:
/*

 MIT License

 Copyright © 2019 Samuel Venable
 Copyright © 2019 Robert B. Colton

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

*/

// USAGE: xtransientfor [window] [command] [title] [icon] [xpos] [ypos] [width] [height]
// [window]: the window id of the window whose command's active window is transient for.
// [command]: the command line to execute and read output from; assumes window creation.
// [title]: the title bar caption text that the command line's active window changes to.
// [icon]: the png icon file which is to be painted on the command line's active window.
// [xpos] [ypos] [width] [height]: x/y position, width, and height of the active window.

// NOTE: xtransientfor waits for the application specified in [command] to open at least
// one window before it will do anything else; this window must be owned by a process id
// which is stored in the _NET_WM_PID atom. If the command launches an executable that's
// not setting the _NET_WM_PID to its own process id on creation, xtransientfor will not
// work; this is done to make sure the window whose properties that are being changed is
// a window that belongs to a child process. the title, icon, size, and x/y pos which is
// specified by xtransientfor's command line parameters can only modify that one window.
// use "center" for x/y position to center the window on your default monitor's display.
// [title], [icon], [xpos], [ypos], [width], and [height] can all optionally be omitted.

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>

#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>

#ifdef __linux__
#include <proc/readproc.h>
#else // BSD
#include <sys/user.h>
#include <libutil.h>
#endif

#include <thread>
#include <chrono>

#include <cstring>

#include <iostream>
#include <string>

#include "lodepng.h"

using std::string;

static inline unsigned nlpo2dc(unsigned x) {
  x--;
  x |= x >> 1;
  x |= x >> 2;
  x |= x >> 4;
  x |= x >> 8;
  return x | (x >> 16);
}

static inline void XSetIcon(Display *display, Window window, const char *icon) {
  XSynchronize(display, True);
  Atom property = XInternAtom(display, "_NET_WM_ICON", True);

  unsigned char *data = nullptr;
  unsigned pngwidth, pngheight;
  unsigned error = lodepng_decode32_file(&data, &pngwidth, &pngheight, icon);
  if (error) return;

  unsigned
    widfull = nlpo2dc(pngwidth) + 1,
    hgtfull = nlpo2dc(pngheight) + 1,
    ih, iw;

  const int bitmap_size = widfull * hgtfull * 4;
  unsigned char *bitmap = new unsigned char[bitmap_size]();

  unsigned i = 0;
  unsigned elem_numb = 2 + pngwidth * pngheight;
  unsigned long *result = new unsigned long[elem_numb]();

  result[i++] = pngwidth;
  result[i++] = pngheight;
  for (ih = 0; ih < pngheight; ih++) {
    unsigned tmp = ih * widfull * 4;
    for (iw = 0; iw < pngwidth; iw++) {
      bitmap[tmp + 0] = data[4 * pngwidth * ih + iw * 4 + 2];
      bitmap[tmp + 1] = data[4 * pngwidth * ih + iw * 4 + 1];
      bitmap[tmp + 2] = data[4 * pngwidth * ih + iw * 4 + 0];
      bitmap[tmp + 3] = data[4 * pngwidth * ih + iw * 4 + 3];
      result[i++] = bitmap[tmp + 0] | (bitmap[tmp + 1] << 8) | (bitmap[tmp + 2] << 16) | (bitmap[tmp + 3] << 24);
      tmp += 4;
    }
  }

  XChangeProperty(display, window, property, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)result, elem_numb);
  XFlush(display);
  delete[] result;
  delete[] bitmap;
  delete[] data;
}

static inline Window XGetActiveWindow(Display *display) {
  unsigned long window;
  unsigned char *prop;

  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;

  int screen = XDefaultScreen(display);
  window = RootWindow(display, screen);

  filter_atom = XInternAtom(display, "_NET_ACTIVE_WINDOW", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);

  unsigned long long_property = prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24);
  XFree(prop);

  return (Window)long_property;
}

static inline pid_t XGetActiveProcessId(Display *display) {
  unsigned long window = XGetActiveWindow(display);
  unsigned char *prop;

  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;

  filter_atom = XInternAtom(display, "_NET_WM_PID", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);

  unsigned long long_property = prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24);
  XFree(prop);

  return (pid_t)long_property;
}

static inline bool WaitForChildPidOfPidToExist(pid_t pid, pid_t ppid) {
  while (pid != ppid) {
    if (pid <= 1) break;
    #ifdef __linux__ // Linux
    proc_t proc_info;
    memset(&proc_info, 0, sizeof(proc_info));
    PROCTAB *pt_ptr = openproc(PROC_FILLSTATUS | PROC_PID, &pid);
    if (readproc(pt_ptr, &proc_info) != 0) { 
      pid = proc_info.ppid;
    }
    closeproc(pt_ptr);
    #else // BSD
    struct kinfo_proc *proc_info = kinfo_getproc(pid);
    if (proc_info) {
      pid = proc_info->ki_ppid;
    }
    free(proc_info);
    #endif
  }
  return (pid == ppid);
}

static inline bool file_exists(const char *fname) {
  return (access(fname, F_OK) != -1);
}

static inline string filename_name(string fname) {
  size_t fp = fname.find_last_of("/");
  return fname.substr(fp + 1);
}

static inline string filename_ext(string fname) {
  fname = filename_name(fname);
  size_t fp = fname.find_last_of(".");
  if (fp == string::npos)
   return "";
  return fname.substr(fp);
}

int main(int argc, const char **argv) {
  string helparg = argv[1] ? argv[1] : "--help";

  if (argc == 2 && helparg == "--help") {
    std::cout << "USAGE: xtransientfor [window] [command] [title] [icon] [xpos] [ypos] [width] [height]" << std::endl;
    std::cout << "[window]: the window id of the window whose command's active window is transient for." << std::endl;
    std::cout << "[command]: the command line to execute and read output from; assumes window creation." << std::endl;
    std::cout << "[title]: the title bar caption text that the command line's active window changes to." << std::endl;
    std::cout << "[icon]: the png icon file which is to be painted on the command line's active window." << std::endl;
    std::cout << "[xpos] [ypos] [width] [height]: x/y position, width, and height of the active window." << std::endl;
    std::cout << "                                                                                     " << std::endl;
    std::cout << "NOTE: xtransientfor waits for the application specified in [command] to open at least" << std::endl;
    std::cout << "one window before it will do anything else; this window must be owned by a process id" << std::endl;
    std::cout << "which is stored in the _NET_WM_PID atom. If the command launches an executable that's" << std::endl;
    std::cout << "not setting the _NET_WM_PID to its own process id on creation, xtransientfor will not" << std::endl;
    std::cout << "work; this is done to make sure the window whose properties that are being changed is" << std::endl;
    std::cout << "a window that belongs to a child process. the title, icon, size, and x/y pos which is" << std::endl;
    std::cout << "specified by xtransientfor's command line parameters can only modify that one window." << std::endl;
    std::cout << "use 'center' for x/y position to center the window on your default monitor's display." << std::endl;
    std::cout << "[title], [icon], [xpos], [ypos], [width], and [height] can all optionally be omitted." << std::endl;
  }

  if (argc >= 3 && argc <= 9) {
    char *buffer = NULL;
    size_t buffer_size = 0;
    string str_buffer;

    FILE *file = popen(argv[2], "r");
    pid_t ppid = getpid();
    pid_t pid = 0;

    if ((pid = fork()) == 0) {
      Display *display = XOpenDisplay(NULL);
      while (!WaitForChildPidOfPidToExist(XGetActiveProcessId(display), ppid));
      Window window = XGetActiveWindow(display);

      Window parent = (Window)strtoul(argv[1], NULL, 10);
      Atom window_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE", True);
      Atom dialog_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", True);
      XChangeProperty(display, window, window_type, XA_ATOM, 32, PropModeReplace, (unsigned char *)&dialog_type, 1);

      XSetTransientForHint(display, window, parent);
      string argv3 = argv[3] ? argv[3] : "";

      if (argc >= 4 && argv3 != "") {
        Atom atom_name = XInternAtom(display,"_NET_WM_NAME", True);
        Atom atom_utf_type = XInternAtom(display,"UTF8_STRING", True);
        XChangeProperty(display, window, atom_name, atom_utf_type, 8, PropModeReplace, (unsigned char *)argv[3], strlen(argv[3]));
      }

      if (argc >= 5 && argv[4] != NULL && file_exists(argv[4]) && filename_ext(argv[4]) == ".png")
        XSetIcon(display, window, argv[4]);
 
      string argv5 = argv[5] ? argv[5] : "center";
      string argv6 = argv[6] ? argv[6] : "center";
 
      if (argc >= 7) {
        int xpos = (int)strtol(argv[5], NULL, 10);
        int ypos = (int)strtol(argv[6], NULL, 10);
        XMoveWindow(display, window, xpos, ypos);
      }
      
      if (argc <= 5 || (argc <= 8 && (argv5 == "center" || argv6 == "center"))) {
        Window child, root = DefaultRootWindow(display); 
        int x0, y0, x1, y1, x2, y2; unsigned w1, h1, w2, h2, border_width, depth;
        XTranslateCoordinates(display, parent, root, 0, 0, &x0, &y0, &child);
        XGetGeometry(display, parent, &root, &x2, &y2, &w2, &h2, &border_width, &depth);
        XGetGeometry(display, window, &root, &x1, &y1, &w1, &h1, &border_width, &depth);
        int xpos = (x0 - x2) + ((w2 - w1) / 2);
        int ypos = (y0 - y2) + ((h2 - h1) / 2);
        XMoveWindow(display, window, xpos, ypos);
      }
 
      if (argc == 9) {
        XSizeHints *hints = XAllocSizeHints(); long supplied;
        XGetWMNormalHints(display, window, hints, &supplied);

        unsigned w1 = (unsigned)strtoul(argv[7], NULL, 10);
        unsigned h1 = (unsigned)strtoul(argv[8], NULL, 10);

        if (hints->min_width != 0 && w1 < hints->min_width) w1 = hints->min_width;
        if (hints->max_width != 0 && w1 > hints->max_width) w1 = hints->max_width;
        if (hints->min_height != 0 && h1 < hints->min_height) h1 = hints->min_height;
        if (hints->max_height != 0 && h1 > hints->max_height) h1 = hints->max_height;

        XResizeWindow(display, window, w1, h1);
   
        if (argv5 == "center" || argv6 == "center") {
          Window child, root = DefaultRootWindow(display);
          int x0, y0, x1, y1, x2, y2; unsigned w2, h2, border_width, depth;
          XTranslateCoordinates(display, parent, root, 0, 0, &x0, &y0, &child);
          XGetGeometry(display, parent, &root, &x2, &y2, &w2, &h2, &border_width, &depth);
          XGetGeometry(display, window, &root, &x1, &y1, &w1, &h1, &border_width, &depth);
          int xpos = (x0 - x2) + ((w2 - w1) / 2);
          int ypos = (y0 - y2) + ((h2 - h1) / 2);
          XMoveWindow(display, window, xpos, ypos);
        }

        XFree(hints);
      }

      XCloseDisplay(display);
      exit(0);
    }

    while (getline(&buffer, &buffer_size, file) != -1)
      str_buffer += buffer;

    std::cout << str_buffer;
    free(buffer);
    pclose(file);

    kill(pid, SIGTERM);
    bool died = false;

    for (unsigned i = 0; !died && i < 5; i++) {
      int status; 
      std::this_thread::sleep_for(std::chrono::seconds(1));
      if (waitpid(pid, &status, WNOHANG) == pid) died = true;
    }

    if (!died) kill(pid, SIGKILL);
  }
}
Usage is mentioned in the code comments directly below the copyright and license information. Requires lodepng.

I originally created this as a patch for a program called zenity because it has two features that are broken for the file and color picker dialogs I had to patch up - setting the title bar icon and making the dialog reliably stay on top of the game window. but it can be used with virtually any command line program, not just zenity. For example, you could launch any program and make it stay on top of your game window during execution to similate the concept of a modal dialog - except the possibilities are endless because you may use any exe you want as the dialog, pass parameters to it and even read the output to your game. Optionally, for programs that support it, you may specify a PNG icon to change the target executable's icon too - the icon that will show on the title bar in some distro's and the task manager.
 
Last edited:

Samuel Venable

Time Killer
I wrote a simple extension for Windows which can convert the image data contents of the clipboard to and from a transparency/alpha-supported png file.
Code:
/*

 MIT License

 Copyright © 2019 Samuel Venable

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

*/

#include <windows.h>

#include <cwchar>
#include <vector>
#include <string>

#include "lodepng.h"

using std::basic_string;
using std::wstring;
using std::string;
using std::vector;
using std::size_t;

#define EXPORTED_FUNCTION extern "C" __declspec(dllexport)

static inline wstring widen(string tstr) {
  size_t wchar_count = tstr.size() + 1;
  vector<wchar_t> buf(wchar_count);
  return wstring{ buf.data(), (size_t)MultiByteToWideChar(CP_UTF8, 0, tstr.c_str(), -1, buf.data(), (int)wchar_count) };
}

static inline string narrow(wstring wstr) {
  int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);
  vector<char> buf(nbytes);
  return string{ buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, NULL, NULL) };
}

static inline bool file_exists(string fname) {
  DWORD file_attr;
  wstring wstr_fname = widen(fname);
  file_attr = GetFileAttributesW(wstr_fname.c_str());
  return (file_attr != INVALID_FILE_ATTRIBUTES && !(file_attr & FILE_ATTRIBUTE_DIRECTORY));
}

EXPORTED_FUNCTION double clipboard_has_imgdata() {
  return IsClipboardFormatAvailable(CF_BITMAP);
}

EXPORTED_FUNCTION double clipboard_load_pngfile(char *fname) {
  if (!file_exists(fname)) return 0;
  unsigned char *src = nullptr;
  unsigned width, height;

  lodepng_decode32_file(&src, &width, &height, fname);

  std::vector<unsigned char> dst;
  int n = width * height * 4;
  dst.resize(n);

  int i = 0;
  for (int y = 0; y < (int)height; y++) {
   for (int x = 0; x < (int)width; x++) {
     int base = (y * width + x) * 4;
     if (src[base + 3] == 0) i++;
   }
  }

  int j = 0;
  for (int y = 0; y < (int)height; y++) {
   for (int x = 0; x < (int)width; x++) {
     int base = (y * width + x) * 4;
     dst[j++] = src[base + 2];
     dst[j++] = src[base + 1];
     dst[j++] = src[base];
     dst[j++] = (i != n / 4) ? src[base + 3] : 255;
   }
  }

  HBITMAP hBitmap = CreateBitmap(width, height, 1, 32, dst.data());

  OpenClipboard(NULL);
  EmptyClipboard();
  SetClipboardData(CF_BITMAP, hBitmap);
  CloseClipboard();

  CloseHandle(hBitmap);
  free(src);
  return 0;
}

EXPORTED_FUNCTION double clipboard_dump_pngfile(char *fname) {
  if (!clipboard_has_imgdata()) return 0;
  OpenClipboard(NULL);
  HBITMAP hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP);
  CloseClipboard();

  BITMAPINFOHEADER bih = { 0 };
  bih.biSize = sizeof(BITMAPINFOHEADER);

  HDC hdc = GetDC(NULL);
  GetDIBits(hdc, hBitmap, 0, (UINT)bih.biHeight, NULL, (BITMAPINFO *)&bih, DIB_RGB_COLORS);
  if (bih.biBitCount <= 8) GetDIBits(hdc, hBitmap, 0, (UINT)bih.biHeight, NULL, (BITMAPINFO *)&bih, DIB_RGB_COLORS);

  unsigned char *src = (unsigned char *)malloc(bih.biWidth * bih.biHeight * 4);
  memset(src, 0, bih.biWidth * bih.biHeight * 4);

  GetDIBits(hdc, hBitmap, 0, (UINT)bih.biHeight, src, (BITMAPINFO *)&bih, DIB_RGB_COLORS);
  ReleaseDC(NULL, hdc);

  std::vector<unsigned char> dst;
  int n = bih.biWidth * bih.biHeight * 4;
  dst.resize(n);

  int i = 0;
  for (int y = bih.biHeight - 1; y >= 0; y--) {
   for (int x = 0; x < bih.biWidth; x++) {
     int base = (y * bih.biWidth + x) * 4;
     if (src[base + 3] == 0) i++;
   }
  }

  int j = 0;
  for (int y = bih.biHeight - 1; y >= 0; y--) {
   for (int x = 0; x < bih.biWidth; x++) {
     int base = (y * bih.biWidth + x) * 4;
     dst[j++] = src[base + 2];
     dst[j++] = src[base + 1];
     dst[j++] = src[base];
     dst[j++] = (i != n / 4) ? src[base + 3] : 255;
   }
  }

  lodepng::encode(fname, dst, bih.biWidth, bih.biHeight);

  CloseHandle(hBitmap);
  free(src);
  return 0;
}
(Requires lodepng).
 
Last edited:

TheSnidr

Heavy metal viking dentist
GMC Elder
Here's a script for finding the roots of polynomials up to 3rd degree. I've only adapted the code in the source link (see the comment section of the script), and I've also added a solver for when the polynomial is second or first degree.
Code:
/// @description cubic_solve(a, b, c, d)
/// @param a
/// @param b
/// @param c
/// @param d
/*
    Finds the roots of polynomials up to third degree. Returns the result as an array of the following format:
    [x1 real, x1 imaginary, x2 real, x2 imaginary, x3 real, x3 imaginary]
    If a root does not exist, it is returned as undefined. Always check if the result is undefined before using it!
   
    Source:
    https://stackoverflow.com/questions/13328676/c-solving-cubic-equations
    Adapted to GML by TheSnidr
*/
var a = argument0;
var b = argument1;
var c = argument2;
var d = argument3;

var x1Re = undefined;
var x1Im = undefined;
var x2Re = undefined;
var x2Im = undefined;
var x3Re = undefined;
var x3Im = undefined;

if (a == 0)
{
    if (b == 0)
    {
        if (c == 0)
        {
            //This is a line without roots
            return [x1Re, x1Im, x2Re, x2Im, x3Re, x3Im];
        }
        //This is a straight line
        x1Re = - d / c;
        x1Im = 0;
        return [x1Re, x1Im, x2Re, x2Im, x3Re, x3Im];
    }
    //This is a quadratic polynomial
    var term1 = c * c - 4 * b * d;
    b *= 2;
    if (term1 > 0)
    {
        //The equaction has two real roots
        term1 = sqrt(term1);
        x1Re = (- c + term1) / b;
        x1Im = 0;
        x2Re = (- c - term1) / b;
        x2Im = 0;
        return [x1Re, x1Im, x2Re, x2Im, x3Re, x3Im];
    }
    if (term1 == 0)
    {
        //The equation only has one root, which is real
        x1Re = - c / b;
        x1Im = 0;
        return [x1Re, x1Im, x2Re, x2Im, x3Re, x3Im];
    }
    //The equation has two complex roots
    x1Re = - c / b;
    x1Im = sqrt(-term1) / b;
    x2Re = - c / b;
    x2Im = - x1Im;
    return [x1Re, x1Im, x2Re, x2Im, x3Re, x3Im];
}

//This is a third degree polynomial
b /= a;
c /= a;
d /= a;

var q = (3 * c - b * b) / 9;
var r = (- 27 * d + b * (9 * c - 2 * b * b)) / 54;
var disc = q * q * q + r * r;
var term1 = b / 3;

if (disc > 0)
{
    //One root is real, two are complex
    var s = r + sqrt(disc);
    s = sign(s) * power(abs(s), 1 / 3);
    var t = r - sqrt(disc);
    t = sign(t) * power(abs(t), 1 / 3);
   
    x1Re = -term1 + s + t;
    term1 += (s + t) * .5;
    x2Re = -term1;
    x3Re = -term1;
    term1 = sqrt(3) * (s - t) * .5;
    x1Im = 0;
    x2Im = term1;
    x3Im = -term1;
    return [x1Re, x1Im, x2Re, x2Im, x3Re, x3Im];
}

if (disc == 0)
{
    //All roots real, at least two are equal
    var r13 = sign(r) * power(abs(r), 1 / 3);
    x1Re = -term1 + 2 * r13;
    x1Im = 0;
    if r13 != 0
    {
        x2Re = -term1 - r13;
        x2Im = 0;
    }
    return [x1Re, x1Im, x2Re, x2Im, x3Re, x3Im];
}

//Only option left is that all roots are real and unequal
q = -q;
var dum1 = arccos(r / sqrt(q * q * q));
var r13 = 2 * sqrt(q);
x1Re = -term1 + r13 * cos(dum1 / 3);
x2Re = -term1 + r13 * cos((dum1 + 2 * pi) / 3);
x3Re = -term1 + r13 * cos((dum1 + 4 * pi) / 3);
x1Im = 0;
x2Im = 0;
x3Im = 0;
return [x1Re, x1Im, x2Re, x2Im, x3Re, x3Im];
 

Samuel Venable

Time Killer
XGetDisplay

XGetDisplay - Print Primary Monitor WidthxHeight+X+Y with X11

This application takes no additional arguments - easy to use!

Statically links all dependencies except -ldl for convenience

If you get build errors you will need to install dependencies

ExampleUsage.c:

Code:
/*

 MIT License

 Copyright © 2020 Samuel Venable

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

*/

#include "XDisplayGetters.h"

#include <stdio.h>
#include <string.h>

int main() {
  char displayWidth[16];
  sprintf(displayWidth, "%d", display_get_width());
  char displayHeight[16];
  sprintf(displayHeight, "%d", display_get_height());
  char displayX[16];
  sprintf(displayX, "%d", display_get_x());
  char displayY[16];
  sprintf(displayY, "%d", display_get_y());
  char cstr[82];
  strcpy(cstr, "Primary Monitor WidthxHeight+X+Y: ");
  strcat(cstr, displayWidth);
  strcat(cstr, "x");
  strcat(cstr, displayHeight);
  strcat(cstr, "+");
  strcat(cstr, displayX);
  strcat(cstr, "+");
  strcat(cstr, displayY);
  puts(cstr);
  return 0;
}
XDisplayGetters.h:
Code:
/*

 MIT License
 Copyright © 2020 Samuel Venable
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
*/

int display_get_x();
int display_get_y();
int display_get_width();
int display_get_height();
XDisplayGetters.c:
Code:
/*

 MIT License

 Copyright © 2020 Samuel Venable

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

*/

#include "XDisplayGetters.h"

#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/Xinerama.h>

static int displayX            = -1;
static int displayY            = -1;
static int displayWidth        = -1;
static int displayHeight       = -1;

static int displayXGetter      = -1;
static int displayYGetter      = -1;
static int displayWidthGetter  = -1;
static int displayHeightGetter = -1;

static void display_get_position(Bool i, int *result) {
  Display *display = XOpenDisplay(NULL);
  *result = 0; Rotation original_rotation;
  Window root = XDefaultRootWindow(display);
  XRRScreenConfiguration *conf = XRRGetScreenInfo(display, root);
  SizeID original_size_id = XRRConfigCurrentConfiguration(conf, &original_rotation);
  if (XineramaIsActive(display)) {
    int m = 0; XineramaScreenInfo *xrrp = XineramaQueryScreens(display, &m);
    if (!i) *result = xrrp[original_size_id].x_org;
    else if (i) *result = xrrp[original_size_id].y_org;
    XFree(xrrp);
  }
  XCloseDisplay(display);
}

static void display_get_size(Bool i, int *result) {
  Display *display = XOpenDisplay(NULL);
  *result = 0; int num_sizes; Rotation original_rotation;
  Window root = XDefaultRootWindow(display);
  int screen = XDefaultScreen(display);
  XRRScreenConfiguration *conf = XRRGetScreenInfo(display, root);
  SizeID original_size_id = XRRConfigCurrentConfiguration(conf, &original_rotation);
  if (XineramaIsActive(display)) {
    XRRScreenSize *xrrs = XRRSizes(display, screen, &num_sizes);
    if (!i) *result = xrrs[original_size_id].width;
    else if (i) *result = xrrs[original_size_id].height;
  } else if (!i) *result = XDisplayWidth(display, screen);
  else if (i) *result = XDisplayHeight(display, screen);
  XCloseDisplay(display);
}

int display_get_x() {
  if (displayXGetter == displayX && displayX != -1)
    return displayXGetter;
  display_get_position(False, &displayXGetter);
  int result = displayXGetter;
  displayX = result;
  return result;
}

int display_get_y() {
  if (displayYGetter == displayY && displayY != -1)
    return displayYGetter;
  display_get_position(True, &displayYGetter);
  int result = displayYGetter;
  displayY = result;
  return result;
}

int display_get_width() {
  if (displayWidthGetter == displayWidth && displayWidth != -1)
    return displayWidthGetter;
  display_get_size(False, &displayWidthGetter);
  int result = displayWidthGetter;
  displayWidth = result;
  return result;
}

int display_get_height() {
  if (displayHeightGetter == displayHeight && displayHeight != -1)
    return displayHeightGetter;
  display_get_size(True, &displayHeightGetter);
  int result = displayHeightGetter;
  displayHeight = result;
  return result;
}
In order to build, you will need these dependencies (Debian/Ubuntu-based Linux example):
Code:
sudo apt-get install git dpkg-dev:amd64 cpp:amd64 binutils:amd64 g++-7:amd64 gcc-7:amd64 g++:amd64 gcc:amd64 g++-multilib:amd64 gcc-multilib:amd64 make:amd64 libstdc++6:amd64 libgcc-8-dev:amd64 libxinerama-dev:amd64 libxrandr-dev:amd64 libxext-dev:amd64 libxrender-dev:amd64 libx11-dev:amd64 libc6-dev:amd64
Build for amd64 Linux and BSD with:
Code:
cd "${0%/*}"
git clone git://github.molgen.mpg.de/git-mirror/glibc.git
./glibc/configure --enable-static-nss
gcc -no-pie XDisplayGetters.c ExampleUsage.c -Wl,-Bstatic -static-libstdc++ -static-libgcc -lXinerama -lXrandr -lXext -lXrender -lX11 -lxcb -lXau -lXdmcp -Wl,-Bdynamic -ldl -m64 -o xgetdisplay-c-amd64 -fPIC -m64
Build for x86_64 Mac via XQuartz with:
Code:
cd "${0%/*}"
clang XDisplayGetters.c ExampleUsage.c -lXinerama -lXrandr -lXext -lXrender -lX11 -lxcb -lXau -lXdmcp -ldl -m64 -o xgetdisplay-c-x86_64 -L/usr/X11/lib -I/usr/X11/include -fPIC -m64
View it on GitHub for a C++ example instead of C as well as support for more architectures and downloadable Linux binaries.

Note the prebuilt binaries can be used with my EvaluateShell extension to return and parse the output printing for use in GM.
 
Last edited:

Samuel Venable

Time Killer
procinfo/win32/procinfo.cpp
C++:
#include "../procinfo.h"
#include <windows.h>
#include <tlhelp32.h>
#include <process.h>
#include <psapi.h>
#include <cstddef>
#include <vector>
#include <cwchar>

using std::string;
using std::to_string;
using std::wstring;
using std::vector;
using std::size_t;

static inline wstring widen(string str) {
  size_t wchar_count = str.size() + 1;
  vector<wchar_t> buf(wchar_count);
  return wstring{ buf.data(), (size_t)MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buf.data(), (int)wchar_count) };
}

static inline string narrow(wstring wstr) {
  int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);
  vector<char> buf(nbytes);
  return string{ buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, NULL, NULL) };
}

static inline HANDLE OpenProcessWithDebugPrivilege(process_t pid) {
  HANDLE hToken;
  LUID luid;
  TOKEN_PRIVILEGES tkp;
  OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
  LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);
  tkp.PrivilegeCount = 1;
  tkp.Privileges[0].Luid = luid;
  tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  AdjustTokenPrivileges(hToken, false, &tkp, sizeof(tkp), NULL, NULL);
  CloseHandle(hToken);
  return OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
}

static string proc_wids;
static process_t proc_pid;
static inline BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lparam) {
  DWORD dw_pid;
  GetWindowThreadProcessId(hWnd, &dw_pid);
  if (dw_pid == proc_pid) {
    void *voidp_wid = reinterpret_cast<void *>(hWnd);
    proc_wids += to_string(reinterpret_cast<unsigned long long>(voidp_wid));
    proc_wids += "|";
  }
  return TRUE;
}

namespace procinfo {

static process_t prevpid;
static string prevout;

process_t process_execute(string command) {
  process_t pid;
  string output;
  wstring wstr_command = widen(command);
  wchar_t cwstr_command[32768];
  wcsncpy(cwstr_command, wstr_command.c_str(), 32768);
  BOOL proceed = TRUE;
  HANDLE hStdInPipeRead = NULL;
  HANDLE hStdInPipeWrite = NULL;
  HANDLE hStdOutPipeRead = NULL;
  HANDLE hStdOutPipeWrite = NULL;
  SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
  proceed = CreatePipe(&hStdInPipeRead, &hStdInPipeWrite, &sa, 0);
  if (proceed == FALSE) return pid;
  proceed = CreatePipe(&hStdOutPipeRead, &hStdOutPipeWrite, &sa, 0);
  if (proceed == FALSE) return pid;
  STARTUPINFOW si = { 0 };
  si.cb = sizeof(STARTUPINFOW);
  si.dwFlags = STARTF_USESTDHANDLES;
  si.hStdError = hStdOutPipeWrite;
  si.hStdOutput = hStdOutPipeWrite;
  si.hStdInput = hStdInPipeRead;
  PROCESS_INFORMATION pi = { 0 };
  if (CreateProcessW(NULL, cwstr_command, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
    while (WaitForSingleObject(pi.hProcess, 5) == WAIT_TIMEOUT) {
      MSG msg;
      if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }
    CloseHandle(hStdOutPipeWrite);
    CloseHandle(hStdInPipeRead);
    char buffer[4096];
    DWORD dwRead = 0;
    proceed = ReadFile(hStdOutPipeRead, buffer, 4096, &dwRead, NULL);
    while (proceed == TRUE) {
      buffer[dwRead] = 0;
      proceed = ReadFile(hStdOutPipeRead, buffer, 4096, &dwRead, NULL);
    }
    CloseHandle(hStdOutPipeRead);
    CloseHandle(hStdInPipeWrite);
    pid = pi.dwProcessId;
    prevpid = pid;
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    output = buffer;
    while (output.back() == '\r' || output.back() == '\n')
      output.pop_back();
    prevout = output;
  }
  return pid;
}

process_t process_previous() {
  return prevpid;
}

string process_evaluate() {
  return prevout;
}

void process_clear_pid() {
  prevpid = 0;
}

void process_clear_out() {
  prevout = "";
}

process_t pid_from_self() {
  return _getpid();
}

process_t ppid_from_self() {
  return ppid_from_pid(pid_from_self());
}

string path_from_pid(process_t pid) {
  string path;
  HANDLE hProcess = OpenProcessWithDebugPrivilege(pid);
  wchar_t szFilename[MAX_PATH]; DWORD dwPathSize = MAX_PATH;
  if (QueryFullProcessImageNameW(hProcess, 0, szFilename, &dwPathSize) != 0) {
    path = narrow(szFilename);
  }
  CloseHandle(hProcess);
  return path;
}

bool pid_exists(process_t pid) {
  // Slower than OpenProcess + GetExitCodeProcess approach,
  // but doesn't return true with processes of a wrong pid.
  // OpenProcess will succeed with a specific number within
  // 3 of any existing pid, which returns true incorrectly.
  bool result = false;
  HANDLE hp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  PROCESSENTRY32 pe = { 0 };
  pe.dwSize = sizeof(PROCESSENTRY32);
  if (Process32First(hp, &pe)) {
    do {
      if (pe.th32ProcessID == pid) {
        result = true;
        break;
      }
    } while (Process32Next(hp, &pe));
  }
  CloseHandle(hp);
  return result;
}

bool wid_exists(wid_t wid) {
  void *voidp_wid = reinterpret_cast<void *>(stoull(wid, nullptr, 10));
  HWND hwnd_wid = reinterpret_cast<HWND>(voidp_wid);
  return (IsWindow(hwnd_wid) && hwnd_wid == GetAncestor(hwnd_wid, GA_ROOT));
}

bool pid_kill(process_t pid) {
  HANDLE hProcess = OpenProcessWithDebugPrivilege(pid);
  if (hProcess == NULL) return false;
  bool result = TerminateProcess(hProcess, 0);
  CloseHandle(hProcess);
  return result;
}

window_t window_from_wid(wid_t wid) {
  return stoull(wid, nullptr, 10);
}

wid_t wid_from_window(window_t window) {
  return to_string(reinterpret_cast<unsigned long long>(window));
}

process_t pid_from_wid(wid_t wid) {
  DWORD dw_pid;
  void *voidp_wid = reinterpret_cast<void *>(stoull(wid, nullptr, 10));
  HWND hwnd_wid = reinterpret_cast<HWND>(voidp_wid);
  GetWindowThreadProcessId(hwnd_wid, &dw_pid);
  return dw_pid;
}

string pids_enum(bool trim_dir, bool trim_empty) {
  string pids = "PID\tPPID\t";
  pids += trim_dir ? "NAME\n" : "PATH\n";
  HANDLE hp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  PROCESSENTRY32 pe = { 0 };
  pe.dwSize = sizeof(PROCESSENTRY32);
  if (Process32First(hp, &pe)) {
    do {
      string exe = trim_dir ?
        name_from_pid(pe.th32ProcessID) :
        path_from_pid(pe.th32ProcessID);
      if (!trim_empty || !exe.empty()) {
        pids += to_string(pe.th32ProcessID) + "\t";
        pids += to_string(pe.th32ParentProcessID) + "\t";
        pids += exe + "\n";
      }
    } while (Process32Next(hp, &pe));
  }
  if (pids.back() == '\n')
    pids.pop_back();
  pids += "\0";
  CloseHandle(hp);
  return pids;
}

process_t ppid_from_pid(process_t pid) {
  process_t ppid;
  HANDLE hp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  PROCESSENTRY32 pe = { 0 };
  pe.dwSize = sizeof(PROCESSENTRY32);
  if (Process32First(hp, &pe)) {
    do {
      if (pe.th32ProcessID == pid) {
        ppid = pe.th32ParentProcessID;
        break;
      }
    } while (Process32Next(hp, &pe));
  }
  CloseHandle(hp);
  return ppid;
}

string pids_from_ppid(process_t ppid) {
  string pids;
  HANDLE hp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  PROCESSENTRY32 pe = { 0 };
  pe.dwSize = sizeof(PROCESSENTRY32);
  if (Process32First(hp, &pe)) {
    do {
      if (pe.th32ParentProcessID == ppid) {
        pids += to_string(pe.th32ProcessID) + "|";
      }
    } while (Process32Next(hp, &pe));
  }
  if (pids.back() == '|')
    pids.pop_back();
  pids += "\0";
  CloseHandle(hp);
  return pids;
}

string wids_from_pid(process_t pid) {
  proc_wids = "";
  proc_pid = pid;
  EnumWindows(EnumWindowsProc, 0);
  if (proc_wids.back() == '|')
    proc_wids.pop_back();
  return proc_wids;
}

wid_t wid_from_top() {
  void *voidp_wid = reinterpret_cast<void *>(GetForegroundWindow());
  return to_string(reinterpret_cast<unsigned long long>(voidp_wid));
}

process_t pid_from_top() {
  return pid_from_wid(wid_from_top());
}

void wid_to_top(wid_t wid) {
  DWORD dw_pid; void *voidp_wid = reinterpret_cast<void *>(stoull(wid, nullptr, 10));
  HWND hwnd_wid = reinterpret_cast<HWND>(voidp_wid);
  GetWindowThreadProcessId(hwnd_wid, &dw_pid);
  AllowSetForegroundWindow(dw_pid);
  SetForegroundWindow(hwnd_wid);
}

void wid_set_pwid(wid_t wid, wid_t pwid) {
  void *voidp_wid = reinterpret_cast<void *>(stoull(wid, nullptr, 10));
  void *voidp_pwid = reinterpret_cast<void *>(stoull(pwid, nullptr, 10));
  HWND hwnd_wid = reinterpret_cast<HWND>(voidp_wid);
  LONG_PTR longp_pwid = reinterpret_cast<LONG_PTR>(voidp_pwid);
  SetWindowLongPtr(hwnd_wid, GWLP_HWNDPARENT, longp_pwid);
}

} // namespace procinfo
procinfo/macosx/procinfo.cpp
C++:
#include "../procinfo.h"
#include <sys/proc_info.h>
#include <libproc.h>
#include <cstdint>
#include <cstring>

typedef void NSWindow;
typedef unsigned long CGWindowID;

using std::string;
using std::to_string;

extern "C" NSWindow *cocoa_window_from_wid(CGWindowID wid);
extern "C" CGWindowID cocoa_wid_from_window(NSWindow *window);
extern "C" bool cocoa_wid_exists(CGWindowID wid);
extern "C" pid_t cocoa_pid_from_wid(CGWindowID wid);
extern "C" const char *cocoa_wids_from_pid(pid_t pid);
extern "C" unsigned long cocoa_get_wid_or_pid(bool wid);
extern "C" void cocoa_wid_to_top(CGWindowID wid);
extern "C" void cocoa_wid_set_pwid(CGWindowID wid, CGWindowID pwid);

namespace procinfo {

string path_from_pid(process_t pid) {
  string path;
  char buffer[PROC_PIDPATHINFO_MAXSIZE];
  if (proc_pidpath(pid, buffer, sizeof(buffer)) > 0) {
    path = string(buffer) + "\0";
  }
  return path;
}

bool wid_exists(wid_t wid) {
  return cocoa_wid_exists(stoul(wid, nullptr, 10));
}

window_t window_from_wid(wid_t wid) {
  unsigned long ul_wid = stoul(wid, nullptr, 10);
  void *voidp_window = reinterpret_cast<void *>(cocoa_window_from_wid(ul_wid));
  return reinterpret_cast<unsigned long long>(voidp_window);
}

wid_t wid_from_window(window_t window) {
  unsigned long long ull_window = reinterpret_cast<unsigned long long>(window);
  void *voidp_window = reinterpret_cast<void *>(ull_window);
  return to_string(cocoa_wid_from_window(voidp_window));
}

process_t pid_from_wid(wid_t wid) {
  return cocoa_pid_from_wid(stoul(wid, nullptr, 10));
}

string pids_enum(bool trim_dir, bool trim_empty) {
  string pids = "PID\tPPID\t";
  pids += trim_dir ? "NAME\n" : "PATH\n";
  int cntp = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
  process_t proc_info[cntp];
  memset(&proc_info, 0, sizeof(proc_info));
  proc_listpids(PROC_ALL_PIDS, 0, proc_info, sizeof(proc_info));
  for (unsigned i = 0; i < cntp; i++) {
    string exe = trim_dir ?
      name_from_pid(proc_info[i]) :
      path_from_pid(proc_info[i]);
    if (!trim_empty || !exe.empty()) {
      if (proc_info[i] == 0) { continue; }
      pids += to_string(proc_info[i]) + "\t";
      pids += to_string(ppid_from_pid(proc_info[i])) + "\t";
      pids += exe + "\n";
    }
  }
  if (pids.back() == '\n')
    pids.pop_back();
  pids += "\0";
  return pids;
}

process_t ppid_from_pid(process_t pid) {
  process_t ppid;
  proc_bsdinfo proc_info;
  if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &proc_info, sizeof(proc_info)) > 0) {
    ppid = proc_info.pbi_ppid;
  }
  return ppid;
}

string pids_from_ppid(process_t ppid) {
  string pids;
  int cntp = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
  process_t proc_info[cntp];
  memset(&proc_info, 0, sizeof(proc_info));
  proc_listpids(PROC_ALL_PIDS, 0, proc_info, sizeof(proc_info));
  for (unsigned i = 0; i < cntp; i++) {
    if (proc_info[i] == 0) { continue; }
    if (ppid_from_pid(proc_info[i]) == ppid) {
      pids += to_string(proc_info[i]) + "|";
    }
  }
  if (pids.back() == '|')
    pids.pop_back();
  pids += "\0";
  return pids;
}

string wids_from_pid(process_t pid) {
  return cocoa_wids_from_pid(pid);
}

wid_t wid_from_top() {
  return to_string(cocoa_get_wid_or_pid(true));
}

process_t pid_from_top() {
  return cocoa_get_wid_or_pid(false);
}

void wid_to_top(wid_t wid) {
  cocoa_wid_to_top(stoul(wid, nullptr, 10));
}

void wid_set_pwid(wid_t wid, wid_t pwid) {
  cocoa_wid_set_pwid(stoul(wid, nullptr, 10), stoul(pwid, nullptr, 10));
}

} // namespace procinfo
procinfo/macosx/cocoa/procinfo.mm
Objective-C:
#import "subclass.h"
#import <Cocoa/Cocoa.h>
#import <sys/types.h>
#import <unistd.h>

NSWindow *cocoa_window_from_wid(CGWindowID wid) {
  return [NSApp windowWithWindowNumber:wid];
}

CGWindowID cocoa_wid_from_window(NSWindow *window) {
  return [window windowNumber];
}

bool cocoa_wid_exists(CGWindowID wid) {
  bool result = false;
  const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
  CFIndex windowCount = 0;
  if ((windowCount = CFArrayGetCount(windowArray))) {
    for (CFIndex i = 0; i < windowCount; i++) {
      NSDictionary *windowInfoDictionary =
      (__bridge NSDictionary *)((CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i));
      NSNumber *ownerPID = (NSNumber *)(windowInfoDictionary[(id)kCGWindowOwnerPID]);
      NSNumber *level = (NSNumber *)(windowInfoDictionary[(id)kCGWindowLayer]);
      if (level.integerValue < kScreensaverWindowLevel) {
        NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
        if (wid == windowID.integerValue) {
          result = true;
          break;
        }
      }
    }
  }
  CFRelease(windowArray);
  return result;
}

pid_t cocoa_pid_from_wid(CGWindowID wid) {
  pid_t pid;
  const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
  CFIndex windowCount = 0;
  if ((windowCount = CFArrayGetCount(windowArray))) {
    for (CFIndex i = 0; i < windowCount; i++) {
      NSDictionary *windowInfoDictionary =
      (__bridge NSDictionary *)((CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i));
      NSNumber *ownerPID = (NSNumber *)(windowInfoDictionary[(id)kCGWindowOwnerPID]);
      NSNumber *level = (NSNumber *)(windowInfoDictionary[(id)kCGWindowLayer]);
      if (level.integerValue < kScreensaverWindowLevel) {
        NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
        if (wid == windowID.integerValue) {
          pid = ownerPID.integerValue;
          break;
        }
      }
    }
  }
  CFRelease(windowArray);
  return pid;
}

const char *cocoa_wids_from_pid(pid_t pid) {
  NSString *wids = [[NSString alloc] init];
  const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
  CFIndex windowCount = 0;
  if ((windowCount = CFArrayGetCount(windowArray))) {
    for (CFIndex i = 0; i < windowCount; i++) {
      NSDictionary *windowInfoDictionary =
      (__bridge NSDictionary *)((CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i));
      NSNumber *ownerPID = (NSNumber *)(windowInfoDictionary[(id)kCGWindowOwnerPID]);
      NSNumber *level = (NSNumber *)(windowInfoDictionary[(id)kCGWindowLayer]);
      if (level.integerValue < kScreensaverWindowLevel) {
        NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
        if (pid == ownerPID.integerValue) {
          wids = [wids stringByAppendingString:[@(windowID.integerValue) stringValue]];
          wids = [wids stringByAppendingString:@"|"];
        }
      }
    }
  }
  if ([wids length] > 0) {
    wids = [wids substringWithRange:NSMakeRange(0, [wids length] - 1)];
  }
  const char *result = [wids UTF8String];
  CFRelease(windowArray);
  [wids release];
  return result;
}

unsigned long cocoa_get_wid_or_pid(bool wid) {
  unsigned long result;
  const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
  CFIndex windowCount = 0;
  if ((windowCount = CFArrayGetCount(windowArray))) {
    for (CFIndex i = 0; i < windowCount; i++) {
      NSDictionary *windowInfoDictionary =
      (__bridge NSDictionary *)((CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i));
      NSNumber *ownerPID = (NSNumber *)(windowInfoDictionary[(id)kCGWindowOwnerPID]);
      NSNumber *level = (NSNumber *)(windowInfoDictionary[(id)kCGWindowLayer]);
      if (level.integerValue == 0) {
        NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
        result = wid ? windowID.integerValue : ownerPID.integerValue;
        break;
      }
    }
  }
  CFRelease(windowArray);
  return result;
}

void cocoa_wid_to_top(CGWindowID wid) {
  CFIndex appCount = [[[NSWorkspace sharedWorkspace] runningApplications] count];
  for (CFIndex i = 0; i < appCount; i++) {
    NSWorkspace *sharedWS = [NSWorkspace sharedWorkspace];
    NSArray *runningApps = [sharedWS runningApplications];
    NSRunningApplication *currentApp = [runningApps objectAtIndex:i];
    pid_t currentPid = [currentApp processIdentifier];
    if (cocoa_pid_from_wid(wid) == currentPid) {
      NSRunningApplication *appWithPID = currentApp;
      NSUInteger options = NSApplicationActivateAllWindows;
      options |= NSApplicationActivateIgnoringOtherApps;
      [appWithPID activateWithOptions:options];
      break;
    }
  }
}

void cocoa_wid_set_pwid(CGWindowID wid, CGWindowID pwid) {
  if (cocoa_pid_from_wid(pwid) == getpid()) {
    [cocoa_window_from_wid(pwid) setChildWindowWithNumber:wid];
  } else if (cocoa_pid_from_wid(wid) == getpid()) {
    [cocoa_window_from_wid(wid) setParentWindowWithNumber:pwid];
  }
}
procinfo/macosx/cocoa/subclass.mm
Objective-C:
#import "subclass.h"
#import <Cocoa/Cocoa.h>
#import <sys/types.h>
#import <unistd.h>

CGWindowID cocoa_wid = kCGNullWindowID;
CGWindowID cocoa_pwid = kCGNullWindowID;

// pid of child wid
pid_t cocoa_pid = 0;

// pid of parent wid
pid_t cocoa_ppid = 0;

void subclass_helper(NSWindow *window) {
  if (cocoa_ppid == getpid() && cocoa_wid_exists(cocoa_wid)) {
    [window setCanHide:NO];
    [window orderWindow:NSWindowBelow relativeTo:cocoa_wid];
  } else if (cocoa_pid == getpid() && cocoa_wid_exists(cocoa_pwid)) {
    [window setCanHide:NO];
    [window orderWindow:NSWindowAbove relativeTo:cocoa_pwid];
    cocoa_wid_to_top(cocoa_wid);
  } else {
    [window setCanHide:YES];
    cocoa_wid = kCGNullWindowID;
    cocoa_pwid = kCGNullWindowID;
    cocoa_pid = 0;
    cocoa_ppid = 0;
  }
}

@implementation NSWindow(subclass)

- (void)setChildWindowWithNumber:(CGWindowID)wid {
  [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(windowDidBecomeKey:)
    name:NSWindowDidUpdateNotification object:self];
  [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(windowDidResignKey:)
    name:NSWindowDidUpdateNotification object:self];
  cocoa_pwid = [self windowNumber]; cocoa_wid = wid;
  cocoa_ppid = cocoa_pid_from_wid(cocoa_pwid);
  cocoa_pid = cocoa_pid_from_wid(cocoa_wid);
  [self orderWindow:NSWindowBelow relativeTo:wid];
}

- (void)setParentWindowWithNumber:(CGWindowID)pwid {
  [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(windowDidBecomeKey:)
    name:NSWindowDidUpdateNotification object:self];
  [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(windowDidResignKey:)
    name:NSWindowDidUpdateNotification object:self];
  cocoa_wid = [self windowNumber]; cocoa_pwid = pwid;
  cocoa_ppid = cocoa_pid_from_wid(cocoa_pwid);
  cocoa_pid = cocoa_pid_from_wid(cocoa_wid);
  [self orderWindow:NSWindowAbove relativeTo:pwid];
}

- (void)windowDidBecomeKey:(NSNotification *)notification {
  subclass_helper(self);
}

- (void)windowDidResignKey:(NSNotification *)notification {
  subclass_helper(self);
}

- (void)windowDidUpdate:(NSNotification *)notification {
  subclass_helper(self);
}

@end
procinfo/macosx/cocoa/subclass.h
Objective-C:
#import <Cocoa/Cocoa.h>
#import <sys/types.h>

bool cocoa_wid_exists(CGWindowID wid);
pid_t cocoa_pid_from_wid(CGWindowID wid);
void cocoa_wid_to_top(CGWindowID wid);

@interface NSWindow(subclass)

- (void)setChildWindowWithNumber:(CGWindowID)wid;
- (void)setParentWindowWithNumber:(CGWindowID)pwid;
- (void)windowDidBecomeKey:(NSNotification *)notification;
- (void)windowDidResignKey:(NSNotification *)notification;
- (void)windowDidUpdate:(NSNotification *)notification;

@end
procinfo/linux/procinfo.cpp
C++:
#include "../procinfo.h"
#include <proc/readproc.h>
#include <cstdlib>
#include <cstring>

using std::string;
using std::to_string;

namespace procinfo {

string path_from_pid(process_t pid) {
  string path;
  string link = string("/proc/") + to_string(pid) + string("/exe");
  char *buffer = realpath(link.c_str(), NULL);
  path = buffer ? : "";
  free(buffer);
  return path;
}

string pids_enum(bool trim_dir, bool trim_empty) {
  proc_t proc_info;
  memset(&proc_info, 0, sizeof(proc_info));
  PROCTAB *proc = openproc(PROC_FILLMEM | PROC_FILLSTAT | PROC_FILLSTATUS);
  string pids = "PID\tPPID\t";
  pids += trim_dir ? "NAME\n" : "PATH\n";
  while (readproc(proc, &proc_info) != 0) {
    string exe = trim_dir ?
      name_from_pid(proc_info.tid) :
      path_from_pid(proc_info.tid);
    if (!trim_empty || !exe.empty()) {
      pids += to_string(proc_info.tid) + "\t";
      pids += to_string(proc_info.ppid) + "\t";
      pids += exe + "\n";
    }
  }
  if (pids.back() == '\n')
    pids.pop_back();
  pids += "\0";
  closeproc(proc);
  return pids;
}

process_t ppid_from_pid(process_t pid) {
  process_t ppid;
  proc_t proc_info;
  memset(&proc_info, 0, sizeof(proc_info));
  PROCTAB *proc = openproc(PROC_FILLSTATUS | PROC_PID, &pid);
  if (readproc(proc, &proc_info) != 0) {
    ppid = proc_info.ppid;
  }
  closeproc(proc);
  return ppid;
}

string pids_from_ppid(process_t ppid) {
  string pids;
  proc_t proc_info;
  memset(&proc_info, 0, sizeof(proc_info));
  PROCTAB *proc = openproc(PROC_FILLMEM | PROC_FILLSTAT | PROC_FILLSTATUS);
  while (readproc(proc, &proc_info) != 0) {
    if (proc_info.ppid == ppid) {
      pids += to_string(proc_info.tid) + "|";
    }
  }
  if (pids.back() == '|')
    pids.pop_back();
  pids += "\0";
  return pids;
}

} // namespace procinfo
procinfo/freebsd/procinfo.cpp
C++:
#include "../procinfo.h"
#include <sys/user.h>
#include <sys/sysctl.h>
#include <libutil.h>
#include <cstdlib>
#include <cstddef>

using std::string;
using std::to_string;
using std::size_t;

namespace procinfo {

string path_from_pid(process_t pid) {
  string path;
  size_t length;
  // CTL_KERN::KERN_PROC::KERN_PROC_PATHNAME(pid)
  int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid };
  if (sysctl(mib, 4, NULL, &length, NULL, 0) == 0) {
    path.resize(length, '\0');
    char *buffer = path.data();
    if (sysctl(mib, 4, buffer, &length, NULL, 0) == 0) {
      path = string(buffer) + "\0";
    }
  }
  return path;
}

string pids_enum(bool trim_dir, bool trim_empty) {
  int cntp;
  string pids = "PID\tPPID\t";
  pids += trim_dir ? "NAME\n" : "PATH\n";
  struct kinfo_proc *proc_info = kinfo_getallproc(&cntp);
  if (proc_info) {
    for (unsigned i = 0; i < cntp; i++) {
      string exe = trim_dir ?
        name_from_pid(proc_info[i].ki_tid) :
        path_from_pid(proc_info[i].ki_tid);
      if (!trim_empty || !exe.empty()) {
        pids += to_string(proc_info[i].ki_tid) + "\t";
        pids += to_string(proc_info[i].ki_ppid) + "\t";
        pids += exe + "\n";
      }
    }
  }
  if (pids.back() == '\n')
    pids.pop_back();
  pids += "\0";
  free(proc_info);
  return pids;
}

process_t ppid_from_pid(process_t pid) {
  process_t ppid;
  struct kinfo_proc *proc_info = kinfo_getproc(pid);
  if (proc_info) {
    ppid = proc_info->ki_ppid;
  }
  free(proc_info);
  return ppid;
}

string pids_from_ppid(process_t ppid) {
  string pids; int cntp;
  struct kinfo_proc *proc_info = kinfo_getallproc(&cntp);
  if (proc_info) {
    for (unsigned i = 0; i < cntp; i++) {
      if (proc_info[i].ki_ppid == ppid) {
        pids += to_string(proc_info[i].ki_tid) + "|";
      }
    }
  }
  if (pids.back() == '|')
    pids.pop_back();
  pids += "\0";
  return pids;
}

} // namespace procinfo
procinfo/xlib/procinfo.cpp
C++:
#include "../procinfo.h"
#include <X11/Xlib.h>

using std::string;
using std::to_string;

static inline int XErrorHandlerImpl(Display *display, XErrorEvent *event) {
  return 0;
}

static inline int XIOErrorHandlerImpl(Display *display) {
  return 0;
}

static inline void SetErrorHandlers() {
  XSetErrorHandler(XErrorHandlerImpl);
  XSetIOErrorHandler(XIOErrorHandlerImpl);
}

static inline unsigned long get_wid_or_pid(Display *display, Window window, bool wid) {
  SetErrorHandlers();
  unsigned char *prop;
  unsigned long property;
  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;
  filter_atom = XInternAtom(display, wid ? "_NET_ACTIVE_WINDOW" : "_NET_WM_PID", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1000, False,
  AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);
  if (status == Success && prop != NULL) {
    property = prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24);
    XFree(prop);
  }
  return property;
}

namespace procinfo {

bool wid_exists(wid_t wid) {
  SetErrorHandlers();
  bool result = false;
  Display *display = XOpenDisplay(NULL);
  Window window = XDefaultRootWindow(display);
  unsigned char *prop;
  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;
  filter_atom = XInternAtom(display, "_NET_CLIENT_LIST", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1024, False,
  AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);
  if (status == Success && prop != NULL && nitems) {
    if (actual_format == 32) {
      unsigned long *array = (unsigned long *)prop;
      for (unsigned i = 0; i < nitems; i++) {
        if (stoul(wid, nullptr, 10) == (unsigned long)array[i]) {
          result = true;
          break;
        }
      }
    }
    XFree(prop);
  }
  XCloseDisplay(display);
  return result;
}

window_t window_from_wid(wid_t wid) {
  return stoull(wid, nullptr, 10);
}

wid_t wid_from_window(window_t window) {
  return to_string(reinterpret_cast<unsigned long long>(window));
}

process_t pid_from_wid(wid_t wid) {
  SetErrorHandlers();
  process_t pid; Window window;
  window = stoul(wid, nullptr, 10);
  if (!window) return pid;
  Display *display = XOpenDisplay(NULL);
  pid = get_wid_or_pid(display, window, false);
  XCloseDisplay(display);
  return pid;
}

string wids_from_pid(process_t pid) {
  SetErrorHandlers();
  string result;
  Display *display = XOpenDisplay(NULL);
  Window window = XDefaultRootWindow(display);
  unsigned char *prop;
  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;
  filter_atom = XInternAtom(display, "_NET_CLIENT_LIST", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1024, False,
  AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);
  if (status == Success && prop != NULL && nitems) {
    if (actual_format == 32) {
      unsigned long *array = (unsigned long *)prop;
      for (unsigned i = 0; i < nitems; i++) {
        string wid = to_string((unsigned long)array[i]);
        if (pid_from_wid(wid) == pid) {
          result += wid + "|";
        }
      }
    }
    XFree(prop);
  }
  if (result.back() == '|')
    result.pop_back();
  XCloseDisplay(display);
  return result;
}

wid_t wid_from_top() {
  SetErrorHandlers();
  wid_t wid; Window window;
  Display *display = XOpenDisplay(NULL);
  int screen = XDefaultScreen(display);
  window = RootWindow(display, screen);
  wid = to_string(get_wid_or_pid(display, window, true));
  XCloseDisplay(display);
  return wid;
}

process_t pid_from_top() {
  return pid_from_wid(wid_from_top());
}

void wid_to_top(wid_t wid) {
  SetErrorHandlers();
  unsigned long window = stoul(wid, nullptr, 10);
  Display *display = XOpenDisplay(NULL);
  XRaiseWindow(display, window);
  XSetInputFocus(display, window, RevertToPointerRoot, CurrentTime);
  XCloseDisplay(display);
}

void wid_set_pwid(wid_t wid, wid_t pwid) {
  SetErrorHandlers();
  Display *display = XOpenDisplay(NULL);
  unsigned long window = stoul(wid, nullptr, 10);
  unsigned long parent = stoul(pwid, nullptr, 10);
  XSetTransientForHint(display, window, parent);
  XCloseDisplay(display);
}

} // namespace procinfo
procinfo/posix/procinfo.cpp
C++:
#include "../procinfo.h"
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <cstdio>

using std::string;

namespace procinfo {

static process_t prevpid;
static string prevout;

process_t process_execute(string command) {
  pid_t pid;
  string output;
  char buffer[BUFSIZ];
  FILE *pf = popen(command.c_str(), "r");
  while (!feof(pf)) {
    output.append(buffer, fread(&buffer, sizeof(char), BUFSIZ, pf));
    pid = wait(NULL);
  }
  prevpid = pid;
  pclose(pf);
  while (output.back() == '\r' || output.back() == '\n')
    output.pop_back();
  prevout = output;
  return pid;
}

process_t process_previous() {
  return prevpid;
}

string process_evaluate() {
  return prevout;
}

void process_clear_pid() {
  prevpid = 0;
}

void process_clear_out() {
  prevout = "";
}

process_t pid_from_self() {
  return getpid();
}

process_t ppid_from_self() {
  return getppid();
}

bool pid_exists(process_t pid) {
  return (kill(pid, 0) == 0);
}

bool pid_kill(process_t pid) {
  return (kill(pid, SIGKILL) == 0);
}

} // namepace procinfo
procinfo/universal/procinfo.cpp
C++:
#include "../procinfo.h"
#include <cstddef>
#include <thread>

using std::string;
using std::size_t;
using std::thread;

namespace procinfo {

void process_execute_async(string command) {
  thread proc_thread(process_execute, command);
  proc_thread.detach();
}

string dir_from_pid(process_t pid) {
  string fname = path_from_pid(pid);
  size_t fp = fname.find_last_of("/\\");
  return fname.substr(0, fp + 1);
}

string name_from_pid(process_t pid) {
  string fname = path_from_pid(pid);
  size_t fp = fname.find_last_of("/\\");
  return fname.substr(fp + 1);
}

} // namespace procinfo
procinfo/procinfo.h
C++:
// individual platforms need their platform-specific
// window types casted to an unsigned long long type
#define window_t unsigned long long

#ifdef _WiN32
#include <windows.h>
#define process_t DWORD
#else
#include <sys/types.h>
#define process_t pid_t
#endif
#include <string>
#define wid_t std::string

namespace procinfo {

// execute process, returns process id
process_t process_execute(std::string command);

// execute process outside main thread
void process_execute_async(std::string command);

// return previous executed process id
process_t process_previous();

// evaluate last process output string
std::string process_evaluate();

// clears previous process id executed
void process_clear_pid();

// clears previous process output text
void process_clear_out();

// get process id from current process
process_t pid_from_self();

// parent process id from this process
process_t ppid_from_self();

// get executable path from process id
std::string path_from_pid(process_t pid);

// get exe parent path from process id
std::string dir_from_pid(process_t pid);

// get executable name from process id
std::string name_from_pid(process_t pid);

// check for existence from process id
bool pid_exists(process_t pid);

// check existence for given window id
bool wid_exists(wid_t wid);

// kill an application from process id
bool pid_kill(process_t pid);

// return window handle from window id
window_t window_from_wid(wid_t wid);

// return window id from window handle
wid_t wid_from_window(window_t window);

// get owner process id from window id
process_t pid_from_wid(wid_t wid);

// return strings for every process id
std::string pids_enum(bool trim_dir, bool trim_empty);

// get parent process id of process id
process_t ppid_from_pid(process_t pid);

// get list of process ids from parent
std::string pids_from_ppid(process_t ppid);

// get list of window ids from process
std::string wids_from_pid(process_t pid);

// get window id from frontmost window
wid_t wid_from_top();

// get process id from topmost process
process_t pid_from_top();

// bring window id to frontmost window
void wid_to_top(wid_t wid);

// add a parent window id to window id
void wid_set_pwid(wid_t wid, wid_t pwid);

} // namespace procinfo
You are welcome to use these functions in your own extensions. I wrote them on my downtime for my own enjoyment. Simply write each of the above files with the specified contents in the specified locations (relative paths to whatever folder you put them in). Then you simply include the header and use the namespace, at the top of whatever C++ source or header files you wish to use them in, like so:
C++:
#include "procinfo/procinfo.h"

using namespace procinfo;

// use functions from header here
Supports Windows, Mac OS X, Linux, and FreeBSD natively. Be sure to only attempt compiling the appropriate files for each platform.

Required files and their associated compiler / linker flags listed for each supported platform below...

Windows:
  • procinfo/win32/procinfo.cpp
  • procinfo/universal/procinfo.cpp
  • procinfo/procinfo.cpp
  • procinfo/procinfo.h
Mac OS X:
  • procinfo/macosx/procinfo.cpp
  • procinfo/macosx/cocoa/procinfo.mm -ObjC -framework Cocoa
  • procinfo/macosx/cocoa/subclass.mm -ObjC -framework Cocoa
  • procinfo/macosx/cocoa/subclass.h
  • procinfo/posix/procinfo.cpp
  • procinfo/universal/procinfo.cpp
  • procinfo/procinfo.cpp
  • procinfo/procinfo.h
Linux:
  • procinfo/linux/procinfo.cpp -lprocps
  • procinfo/posix/procinfo.cpp
  • procinfo/xlib/procinfo.cpp -lX11
  • procinfo/universal/procinfo.cpp
  • procinfo/procinfo.cpp
  • procinfo/procinfo.h
FreeBSD:
  • procinfo/freebsd/procinfo.cpp -lutil -lc
  • procinfo/posix/procinfo.cpp
  • procinfo/xlib/procinfo.cpp -lX11
  • procinfo/universal/procinfo.cpp
  • procinfo/procinfo.cpp
  • procinfo/procinfo.h
Ubuntu / Debian based Linux distro's need some additional headers installed before it can compile:
Code:
sudo apt-get install libx11-dev libprocps-dev
 
Last edited:

Samuel Venable

Time Killer
Transitioning to C++17 with my FileManager Extension. Here's the re-write's source code:

FileManager.cpp
C++:
#include "FileManip/fileman.h"

using std::string;

#ifdef _WIN32
#define EXPORTED_FUNCTION extern "C" __declspec(dllexport)
#else /* macOS, Linux, and BSD */
#define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))
#endif

EXPORTED_FUNCTION char *get_working_directory() {
  static string result;
  result = fileman::get_working_directory();
  return (char *)result.c_str();
}

EXPORTED_FUNCTION double set_working_directory(char *dname) {
  return fileman::set_working_directory(dname);
}

EXPORTED_FUNCTION char *get_temp_directory() {
  static string result;
  result = fileman::get_temp_directory();
  return (char *)result.c_str();
}

EXPORTED_FUNCTION char *get_program_directory() {
  static string result;
  result = fileman::get_program_directory();
  return (char *)result.c_str();
}

EXPORTED_FUNCTION char *get_program_filename() {
  static string result;
  result = fileman::get_program_filename();
  return (char *)result.c_str();
}

EXPORTED_FUNCTION char *get_program_pathname() {
  static string result;
  result = fileman::get_program_pathname();
  return (char *)result.c_str();
}

EXPORTED_FUNCTION char *filename_absolute(char *fname) {
  static string result;
  result = fileman::filename_absolute(fname);
  return (char *)result.c_str();
}

EXPORTED_FUNCTION double file_exists(char *fname) {
  return fileman::file_exists(fname);
}

EXPORTED_FUNCTION double file_delete(char *fname) {
  return fileman::file_delete(fname);
}

EXPORTED_FUNCTION double file_rename(char *oldname, char *newname) {
  return fileman::file_rename(oldname, newname);
}

EXPORTED_FUNCTION double file_copy(char *fname, char *newname) {
  return fileman::file_copy(fname, newname);
}

EXPORTED_FUNCTION double file_size(char *fname) {
  return fileman::file_size(fname);
}

EXPORTED_FUNCTION double directory_exists(char *dname) {
  return fileman::directory_exists(dname);
}

EXPORTED_FUNCTION double directory_create(char *dname) {
  return fileman::directory_create(dname);
}

EXPORTED_FUNCTION double directory_destroy(char *dname) {
  return fileman::directory_destroy(dname);
}

EXPORTED_FUNCTION double directory_rename(char *oldname, char *newname) {
  return fileman::directory_rename(oldname, newname);
}

EXPORTED_FUNCTION double directory_copy(char *dname, char *newname) {
  return fileman::directory_copy(dname, newname);
}

EXPORTED_FUNCTION double directory_size(char *dname) {
  return fileman::directory_size(dname);
}

EXPORTED_FUNCTION char *directory_contents(char *dname) {
  static string result;
  result = fileman::directory_contents(dname);
  return (char *)result.c_str();
}

EXPORTED_FUNCTION char *directory_contents_ext(char *dname, char *pattern, double includedirs) {
  static string result;
  result = fileman::directory_contents(dname, pattern, includedirs);
  return (char *)result.c_str();
}

EXPORTED_FUNCTION char *environment_get_variable(char *name) {
  static string result;
  result = fileman::environment_get_variable(name);
  return (char *)result.c_str();
}

EXPORTED_FUNCTION double environment_set_variable(char *name, char *value) {
  return fileman::environment_set_variable(name, value);
}
FileManip/fileman.h
C++:
#include <string>
#include <vector>
#include <cstddef>

namespace strings {

  std::string string_replace_all(std::string str, std::string substr, std::string nstr);
  std::vector<std::string> string_split(std::string str, char delimiter);
  std::string filename_path(std::string fname);
  std::string filename_name(std::string fname);
  std::string filename_ext(std::string fname);
  std::string filename_normalize(std::string fname);
  std::string filename_remove_slash(std::string dname, bool normalize = false);
  std::string filename_add_slash(std::string dname, bool normalize = false);

} // namespace slashes

namespace fileman {

  std::string get_working_directory_ns();
  bool set_working_directory_ns(std::string dname);
  std::string get_temp_directory_ns();
  std::string get_program_directory_ns();
  std::string get_program_filename_ns();
  std::string get_program_pathname_ns(bool print = true);
  std::string filename_absolute_ns(std::string fname);
  bool file_exists_ns(std::string fname);
  bool file_delete_ns(std::string fname);
  bool file_rename_ns(std::string oldname, std::string newname);
  bool file_copy_ns(std::string fname, std::string newname);
  bool directory_exists_ns(std::string dname);
  bool directory_create_ns(std::string dname);
  bool directory_destroy_ns(std::string dname);
  bool directory_rename_ns(std::string oldname, std::string newname);
  bool directory_copy_ns(std::string dname, std::string newname);
  std::string directory_contents_ns(std::string dname, std::string pattern = "*.*", bool includedirs = true);
  std::string environment_get_variable_ns(std::string name);
  bool environment_set_variable_ns(std::string name, std::string value);

} // namespace fileman
FileManip/Win32/fileman.cpp
C++:
#include "../fileman.h"
#include <windows.h>
#include <Shlobj.h>
#include <iostream>
#include <cstddef>
#include <cwchar>

using std::string;
using std::wstring;
using std::vector;
using std::size_t;
using std::cout;
using std::endl;

namespace {

  wstring widen(string str) {
    size_t wchar_count = str.size() + 1;
    vector<wchar_t> buf(wchar_count);
    return wstring{ buf.data(), (size_t)MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buf.data(), (int)wchar_count) };
  }

  string narrow(wstring wstr) {
    int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);
    vector<char> buf(nbytes);
    return string{ buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, NULL, NULL) };
  }

} // anonymous namespace

namespace strings {

  string filename_normalize(string fname) {
    fname = string_replace_all(fname, "/", "\\");
    wchar_t buffer[MAX_PATH];
    wstring u8fname = widen(fname);
    if (GetFullPathNameW(u8fname.c_str(), MAX_PATH, buffer, NULL) != 0) {
      u8fname = buffer;
    }
    bool has_slash = false;
    while (u8fname.back() == L'\\') {
      has_slash = true;
      u8fname.pop_back();
    }
    LPMALLOC pMalloc;
    PIDLIST_ABSOLUTE path = NULL;
    if (SUCCEEDED(SHParseDisplayName(u8fname.c_str(), NULL, &path, 0, 0)) && path != NULL) {
      if (SHGetPathFromIDListW(path, buffer)) {
        fname = narrow(buffer);
      }
      if (SUCCEEDED(SHGetMalloc(&pMalloc))) {
        pMalloc->Free(path);
        pMalloc->Release();
      }
    } else {
      string name = filename_name(fname);
      fname = filename_path(fname);
      while (fname.back() == '\\') fname.pop_back();
      u8fname = widen(fname);
      if (SUCCEEDED(SHParseDisplayName(u8fname.c_str(), NULL, &path, 0, 0)) && path != NULL) {
        if (SHGetPathFromIDListW(path, buffer)) {
          fname = narrow(buffer);
          if (fname.back() != '\\') fname += "\\";
          fname += name;
        }
        if (SUCCEEDED(SHGetMalloc(&pMalloc))) {
          pMalloc->Free(path);
          pMalloc->Release();
        }
      } else {
        return filename_normalize(fname);
      }
    }
    if (has_slash && fname.back() != '\\')
      fname += "\\";
    return fname;
  }

  string filename_remove_slash(string dname, bool normalize) {
    if (normalize) dname = filename_normalize(dname);
    if (dname.back() == '\\') dname.pop_back();
    return dname;
  }

  string filename_add_slash(string dname, bool normalize) {
    if (normalize) dname = filename_normalize(dname);
    if (dname.back() != '\\') dname += "\\";
    return dname;
  }

} // namespace strings

namespace fileman {

  bool set_working_directory_ns(string dname) {
    wstring u8dname = widen(strings::filename_remove_slash(filename_absolute_ns(dname)));
    return (SetCurrentDirectoryW(u8dname.c_str()) != 0);
  }

  string get_program_pathname_ns(bool print) {
    string path;
    wchar_t buffer[MAX_PATH];
    if (GetModuleFileNameW(NULL, buffer, MAX_PATH) != 0) {
      path = narrow(buffer);
      if (print) {
        cout << "program_pathname = \"" << path << "\"" << endl;
      }
    }
    return path;
  }

  string environment_get_variable_ns(string name) {
    string value;
    wchar_t buffer[32767];
    wstring u8name = widen(name);
    if (GetEnvironmentVariableW(u8name.c_str(), buffer, 32767) != 0) {
      value = narrow(buffer);
    }
    return value;
  }

  bool environment_set_variable_ns(string name, string value) {
    wstring u8name = widen(name);
    wstring u8value = widen(value);
    if (value == "") return (SetEnvironmentVariableW(u8name.c_str(), NULL) != 0);
    return (SetEnvironmentVariableW(u8name.c_str(), u8value.c_str()) != 0);
  }

} // namespace fileman
FileManip/MacOSX/fileman.cpp
C++:
#include "../fileman.h"
#include <libproc.h>
#include <unistd.h>
#include <iostream>

using std::string;
using std::cout;
using std::endl;

namespace fileman {

  string get_program_pathname_ns(bool print) {
    string path;
    char buffer[PROC_PIDPATHINFO_MAXSIZE];
    if (proc_pidpath(getpid(), buffer, sizeof(buffer)) > 0) {
      path = string(buffer) + "\0";
    }
    if (!path.empty()) {
      if (print) {
        cout << "program_pathname = \"" << path << "\"" << endl;
      }
    }
    return path;
  }

} // namespace fileman
FileManip/Linux/fileman.cpp
C++:
#include "../fileman.h"
#include <iostream>
#include <cstdlib>

using std::string;
using std::cout;
using std::endl;

namespace fileman {

  string get_program_pathname_ns(bool print) {
    string path;
    char *buffer = realpath("/proc/self/exe", NULL);
    path = buffer ? : "";
    free(buffer);
    if (!path.empty()) {
      if (print) {
        cout << "program_pathname = \"" << path << "\"" << endl;
      }
    }
    return path;
  }

} // namespace fileman
FileManip/BSD/fileman.cpp
C++:
#include "../fileman.h"
#include <sys/types.h>
#include <sys/sysctl.h>
#include <iostream>
#include <cstddef>

using std::string;
using std::size_t;
using std::cout;
using std::endl;

namespace fileman {

  string get_program_pathname_ns(bool print) {
    string path;
    size_t length;
    // CTL_KERN::KERN_PROC::KERN_PROC_PATHNAME(-1)
    int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
    if (sysctl(mib, 4, NULL, &length, NULL, 0) == 0) {
      path.resize(length, '\0');
      char *buffer = path.data();
      if (sysctl(mib, 4, buffer, &length, NULL, 0) == 0) {
        path = string(buffer) + "\0";
      }
    }
    if (!path.empty()) {
      if (print) {
        cout << "program_pathname = \"" << path << "\"" << endl;
      }
    }
    return path;
  }

} // namespace fileman
FileManip/POSIX/fileman.cpp
C++:
#include "../fileman.h"
#include <unistd.h>
#include <cstdlib>

using std::string;

namespace strings {

  string filename_normalize(string fname) {
    bool has_slash = false;
    while (fname.back() == '/') {
      has_slash = true;
      fname.pop_back();
    }
    char *buffer = realpath(fname.c_str(), NULL);
    if (buffer) {
      fname = buffer;
      free(buffer);
    } else {
      string name = filename_name(fname);
      fname = filename_path(fname);
      char *buffer = realpath(fname.c_str(), NULL);
      if (buffer) {
        fname = buffer;
        free(buffer);
        if (fname.back() != '/') fname += "/";
        fname += name;
      } else {
        return filename_normalize(fname);
      }
    }
    if (has_slash && fname.back() != '/')
      fname += "/";
    return fname;
  }

  string filename_remove_slash(string dname, bool normalize) {
    if (normalize) dname = filename_normalize(dname);
    if (dname.back() == '/') dname.pop_back();
    return dname;
  }

  string filename_add_slash(string dname, bool normalize) {
    if (normalize) dname = filename_normalize(dname);
    if (dname.back() != '/') dname += "/";
    return dname;
  }

} // namespace strings

namespace fileman {

  bool set_working_directory_ns(string dname) {
    return (chdir(strings::filename_remove_slash(filename_absolute_ns(dname)).c_str()) == 0);
  }

  string environment_get_variable_ns(string name) {
    char *value = getenv(name.c_str());
    return value ? value : "";
  }

  bool environment_set_variable_ns(string name, string value) {
    if (value == "") return (unsetenv(name.c_str()) == 0);
    return (setenv(name.c_str(), value.c_str(), 1) == 0);
  }

} // namespace fileman
FileManip/Universal/fileman.cpp
C++:
#include "../fileman.h"
#include <filesystem>
#include <iostream>
#include <sstream>
#include <set>

namespace fs = std::filesystem;

using namespace strings;

using std::string;
using std::vector;
using std::size_t;
using std::cout;
using std::endl;

namespace strings {

  string string_replace_all(string str, string substr, string nstr) {
    size_t pos = 0;
    while ((pos = str.find(substr, pos)) != string::npos) {
      str.replace(pos, substr.length(), nstr);
      pos += nstr.length();
    }
    return str;
  }

  vector<string> string_split(string str, char delimiter) {
    vector<string> vec;
    std::stringstream sstr(str);
    string tmp;
    while (std::getline(sstr, tmp, delimiter))
      vec.push_back(tmp);
    return vec;
  }

  string filename_path(string fname) {
    size_t fp = fname.find_last_of("/\\");
    return fname.substr(0,fp + 1);
  }

  string filename_name(string fname) {
    size_t fp = fname.find_last_of("/\\");
    return fname.substr(fp + 1);
  }

  string filename_ext(string fname) {
    fname = filename_name(fname);
    size_t fp = fname.find_last_of(".");
    if (fp == string::npos)
      return "";
    return fname.substr(fp);
  }

} // namespace strings

namespace fileman {

  string get_working_directory_ns() {
    string result = "";
    try {
      result = filename_add_slash(fs::current_path().u8string());
      if (!result.empty()) cout << "working_directory = \"" << result << "\"" << endl;
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  string get_temp_directory_ns() {
    string result = "";
    try {
      result = filename_add_slash(fs::temp_directory_path().u8string());
      if (!result.empty()) cout << "temp_directory = \"" << result << "\"" << endl;
    } catch (const fs::filesystem_error& e) {
      result = environment_get_variable_ns("TMP");
      if (result.empty()) result = environment_get_variable_ns("TEMP");
      if (result.empty()) result = environment_get_variable_ns("USERPROFILE");
      if (result.empty()) result = environment_get_variable_ns("WINDIR");
      if (!result.empty()) {
        result = filename_add_slash(result);
        cout << "temp_directory = \"" << result << "\"" << endl;
      } else {
        cout << e.what() << endl;
      }
    }
    return result;
  }

  string get_program_directory_ns() {
    string result = filename_path(get_program_pathname_ns(false));
    if (!result.empty()) cout << "program_directory = \"" << result << "\"" << endl;
    return result;
  }

  string get_program_filename_ns() {
    string result = filename_name(get_program_pathname_ns(false));
    if (!result.empty()) cout << "program_filename = \"" << result << "\"" << endl;
    return result;
  }

  string filename_absolute_ns(string fname) {
    string result = "";
    try {
      if (directory_exists_ns(fname)) {
        result = filename_add_slash(fname, true);
      } else if (file_exists_ns(fname)) {
        result = filename_normalize(fname);
      }
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  static inline std::uintmax_t file_size(string fname) {
    fname = filename_normalize(fname);
    std::uintmax_t result = 0;
    try {
      const fs::path path = fs::u8path(fname);
      result = fs::file_size(path);
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  bool file_exists_ns(string fname) {
    fname = filename_normalize(fname);
    bool result = false;
    try {
      const fs::path path = fs::u8path(fname);
      result = (fs::exists(path) && (!fs::is_directory(path)));
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  bool file_delete_ns(string fname) {
    fname = filename_normalize(fname);
    cout << "try: file_delete(\"" << fname << "\")" << endl;
    bool result = false;
    try {
      const fs::path path = fs::u8path(fname);
      if (file_exists_ns(fname)) {
        result = fs::remove(path);
      }
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  bool file_rename_ns(string oldname, string newname) {
    if (!directory_exists_ns(filename_path(newname)))
      directory_create_ns(filename_path(newname));
    oldname = filename_normalize(oldname);
    newname = filename_normalize(newname);
    cout << "try: file_rename(\"" << oldname << "\", \"" << newname << "\")" << endl;
    bool result = false;
    try {
      const fs::path path1 = fs::u8path(oldname);
      const fs::path path2 = fs::u8path(newname);
      if (file_exists_ns(oldname)) {
        std::uintmax_t oldsize = file_size(oldname);
        fs::rename(path1, path2);
        result = (file_exists_ns(newname) && oldsize == file_size(newname));
      }
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  bool file_copy_ns(string fname, string newname) {
    if (!directory_exists_ns(filename_path(newname)))
      directory_create_ns(filename_path(newname));
    fname = filename_normalize(fname);
    newname = filename_normalize(newname);
    cout << "try: file_copy(\"" << fname << "\", \"" << newname << "\")" << endl;
    bool result = false;
    try {
      const fs::path path1 = fs::u8path(fname);
      const fs::path path2 = fs::u8path(newname);
      if (file_exists_ns(fname)) {
        fs::copy(path1, path2);
        result = (file_exists_ns(newname) && file_size(fname) == file_size(newname));
      }
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  static inline std::uintmax_t directory_size(string dname) {
    std::uintmax_t result = 0;
    try {    
      const fs::path path = fs::u8path(filename_remove_slash(dname, true));
      if (fs::exists(path)) {
        fs::directory_iterator end_itr;
        for (fs::directory_iterator dir_ite(path); dir_ite != end_itr; dir_ite++) {
          fs::path file_path(filename_absolute_ns(dir_ite->path().u8string()));
          if (!fs::is_directory(dir_ite->status())) {
            result += file_size(file_path.u8string());
          } else {
            result += directory_size(file_path.u8string());
          }
        }
      }
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  bool directory_exists_ns(string dname) {
    dname = filename_remove_slash(dname, true);
    bool result = false;
    try {
      const fs::path path = fs::u8path(dname);
      result = (fs::exists(path) && fs::is_directory(path));
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  bool directory_create_ns(string dname) {
    dname = filename_remove_slash(dname, true);
    bool result = false;
    try {
      const fs::path path = fs::u8path(dname);
      result = ((!fs::exists(path)) && fs::create_directory(path));
      while (!directory_exists_ns(dname) && directory_create_ns(dname));
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  bool directory_destroy_ns(string dname) {
    dname = filename_remove_slash(dname, true);
    cout << "try: directory_destroy(\"" << dname << "\")" << endl;
    bool result = false;
    try {
      const fs::path path = fs::u8path(dname);
      if (directory_exists_ns(dname)) {
        result = fs::remove_all(path);
      }
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  bool directory_rename_ns(string oldname, string newname) {
    if (!directory_exists_ns(newname)) directory_create_ns(newname);
    oldname = filename_remove_slash(oldname, true);
    newname = filename_remove_slash(newname, true);
    cout << "try: directory_rename(\"" << oldname << "\", \"" << newname << "\")" << endl;
    bool result = false;
    try {
      const fs::path path1 = fs::u8path(oldname);
      const fs::path path2 = fs::u8path(newname);
      const fs::path path3 = fs::u8path(path2.u8string().substr(0, path1.u8string().length()));
      if (directory_exists_ns(oldname)) {
        if ((filename_name(path1.u8string()) != filename_name(path2.u8string()) &&
          filename_path(path1.u8string()) == filename_path(path2.u8string())) ||
          path1.u8string() != path3.u8string()) {
          std::uintmax_t oldsize = directory_size(oldname);
          fs::rename(path1, path2);
          result = (directory_exists_ns(newname) && oldsize == directory_size(newname));
        }
      }
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  static string retained_string = "";
  static size_t retained_length = 0;
  // this function was written to prevent infinitely copying inside itself
  static inline bool directory_copy_ns_retained(string dname, string newname) {
    bool result = false;
    try {
      const fs::path path1 = fs::u8path(dname);
      const fs::path path2 = fs::u8path(newname);
      const fs::path path3 = fs::u8path(path2.u8string().substr(0, path1.u8string().length()));
      if (retained_string.empty() && retained_length == 0) {
        retained_length = path1.u8string().length();
        retained_string = path2.u8string().substr(retained_length);
      }
      if (directory_exists_ns(dname)) {
        if ((filename_name(path1.u8string()) != filename_name(path2.u8string()) &&
          filename_path(path1.u8string()) == filename_path(path2.u8string())) ||
          path1.u8string() != path3.u8string()) {
          fs::copy(path1, path2, fs::copy_options::recursive);
          result = (directory_exists_ns(newname) && directory_size(dname) == directory_size(newname));
        } else if (path1.u8string() == path3.u8string()) {
          vector<string> itemVec = string_split(directory_contents_ns(dname), '\n');
          if (!directory_exists_ns(newname)) {
            directory_create_ns(newname);
            for (const string &item : itemVec) {
              if (directory_exists_ns(filename_remove_slash(item)) &&
                filename_remove_slash(item).substr(retained_length) != retained_string) {
                directory_copy_ns_retained(filename_remove_slash(item), filename_add_slash(path2.u8string()) +
                  filename_name(filename_remove_slash(item)));
              } else if (file_exists_ns(item)) {
                fs::copy(item, filename_add_slash(path2.u8string()) + filename_name(item));
              }
            }
          }
          return (directory_exists_ns(newname) && (directory_size(dname) * 2) >= directory_size(newname));
        }
      }
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    return result;
  }

  bool directory_copy_ns(string dname, string newname) {
    if (!directory_exists_ns(newname)) directory_create_ns(newname);
    dname = filename_remove_slash(dname, true);
    newname = filename_remove_slash(newname, true);
    cout << "try: directory_copy(\"" << dname << "\", \"" << newname << "\")" << endl;
    retained_string = "";
    retained_length = 0;
    return directory_copy_ns_retained(dname, newname);
  }

  string directory_contents_ns(string dname, string pattern, bool includedirs) {
    string result = "";
    try {
      const fs::path path = fs::u8path(filename_remove_slash(dname, true));
      if (fs::exists(path)) {
        fs::directory_iterator end_itr;
        for (fs::directory_iterator dir_ite(path); dir_ite != end_itr; dir_ite++) {
          fs::path file_path(filename_absolute_ns(dir_ite->path().u8string()));
          if (!fs::is_directory(dir_ite->status())) {
            result += file_path.u8string() + "\n";
          } else if (includedirs) {
            result += filename_add_slash(file_path.u8string()) + "\n";
          }
        }
      }
    } catch (const fs::filesystem_error& e) {
      cout << e.what() << endl;
    }
    if (pattern.empty()) pattern = "*.*";
    if (result.back() == '\n') result.pop_back();
    pattern = string_replace_all(pattern, " ", "");
    pattern = string_replace_all(pattern, "*", "");
    vector<string> itemVec = string_split(result, '\n');
    vector<string> extVec = string_split(pattern, ';');
    std::set<string> filteredItems;
    for (const string &item : itemVec) {
      for (const string &ext : extVec) {
        if (ext == "." || ext == filename_ext(item) || directory_exists_ns(item)) {
          filteredItems.insert(item);
          break;
        }
      }
    }
    result = "";
    if (filteredItems.empty()) return result;
    for (const string &filteredName : filteredItems) {
      result += filteredName + "\n";
    }
    result.pop_back();
    return result;
  }

} // namespace fileman
-----------------------------------------------------------

Build Scripts

"Win32 (x86).sh"
Code:
cd "${0%/*}"
mkdir "FileManager (x86)"
g++ FileManip/Win32/fileman.cpp FileManip/Universal/fileman.cpp FileManip.cpp -o "FileManager (x86)/FileManager.dll" -std=c++17 -shared -static-libgcc -static-libstdc++ -static -lshell32 -m32
"Win32 (x64).sh"
Code:
cd "${0%/*}"
mkdir "FileManager (x64)"
g++ FileManip/Win32/fileman.cpp FileManip/Universal/fileman.cpp FileManip.cpp -o "FileManager (x64)/FileManager.dll" -std=c++17 -shared -static-libgcc -static-libstdc++ -static -lshell32 -m64
"MacOSX (x86).sh"
Code:
cd "${0%/*}"
mkdir "FileManager (x86)"
clang++ FileManip/MacOSX/fileman.cpp FileManip/POSIX/fileman.cpp FileManip/Universal/fileman.cpp FileManip.cpp -o "FileManager (x86)/FileManager.dylib" -std=c++17 -shared -m32 -fPIC
"MacOSX (x64).sh"
Code:
cd "${0%/*}"
mkdir "FileManager (x64)"
clang++ FileManip/MacOSX/fileman.cpp FileManip/POSIX/fileman.cpp FileManip/Universal/fileman.cpp FileManip.cpp -o "FileManager (x64)/FileManager.dylib" -std=c++17 -shared -m64 -fPIC
"Linux (x86).sh"
Code:
cd "${0%/*}"
mkdir "FileManager (x86)"
g++ FileManip/Linux/fileman.cpp FileManip/POSIX/fileman.cpp FileManip/Universal/fileman.cpp FileManip.cpp -o "FileManager (x86)/FileManager.so" -std=c++17 -shared -static-libgcc -static-libstdc++ -m32 -fPIC
"Linux (x64).sh"
Code:
cd "${0%/*}"
mkdir "FileManager (x64)"
g++ FileManip/Linux/fileman.cpp FileManip/POSIX/fileman.cpp FileManip/Universal/fileman.cpp FileManip.cpp -o "FileManager (x64)/FileManager.so" -std=c++17 -shared -static-libgcc -static-libstdc++ -m64 -fPIC
"BSD (x86).sh"
Code:
cd "${0%/*}"
mkdir "FileManager (x86)"
g++ FileManip/BSD/fileman.cpp FileManip/POSIX/fileman.cpp FileManip/Universal/fileman.cpp FileManip.cpp -o "FileManager (x86)/FileManager.so" -std=c++17 -shared -static-libgcc -static-libstdc++ -m32 -fPIC
"BSD (x64).sh"
Code:
cd "${0%/*}"
mkdir "FileManager (x64)"
g++ FileManip/BSD/fileman.cpp FileManip/POSIX/fileman.cpp FileManip.cpp FileManip/Universal/fileman.cpp FileManip.cpp -o "FileManager (x64)/FileManager.so" -std=c++17 -shared -static-libgcc -static-libstdc++ -m64 -fPIC
The extension's prebuilt binaries and gmx/yyp demo projects can be downloaded here: https://drive.google.com/drive/folders/1nx62yTl3BUGXgCYhnFDjoMo3Yw5GGRPZ
 
Last edited:

Samuel Venable

Time Killer
Wanted to give the update that my previous post directly above has been edited to support recursive folder creation with all functions. Meaning, as for one example, you may copy a file or directory to a destination path that doesn't even exist yet, and it will recursively create directories automatically until the destination path exists, then it will actually copy the file or directory inside that specified path. It will also now print to the debug console the exact file operations being attempted, displaying each file or directory used in the operation as an absolute path. And lastly, I fixed yet another bug I found with attempting to copy a directory inside itself. Next I am going to replace the exceptions with the proper failure case checks.
 
Last edited:

rytan451

Member
Make a bounding box stretches to fit the window as requested. If stretch_mode is NONE, then the bounding box is not stretched and is centered in the window. If stretch_mode is ASPECT, then the bounding box is centered on the window, and stretched such that it maintains its aspect ratio, but is as large as possible to fit within the window. If Stretching is FULL, then it returns the bounding box of the window.

By "game window" referenced in the manual for what the window's width and height is, I believe they mean the back buffer within the window that is drawn to the screen.

GML:
/// @func stretched_to_fit_window
/// @param width
/// @param height
/// @param stretch_mode

enum Stretching {
  NONE,
  ASPECT,
  FULL
}

var width = argument0,
    height = argument1,
    window_width = window_get_width(),
    window_height = window_get_height();

switch (argument2) {
  case Stretching.NONE:
    return [(window_width - width) / 2, (window_height - height) / 2, width, height];
  case Stretching.ASPECT:
    var window_aspect_ratio = window_width/window_height,
        application_surface_aspect_ratio = width/height;
    var stretched_application_surface_width, stretched_application_surface_height;
    if (window_aspect_ratio < application_surface_aspect_ratio) {
      // Set heights to equal
      stretched_application_surface_height = window_height;
      stretched_application_surface_width = width * stretched_application_surface_height / height;
    } else if (window_aspect_ratio > application_surface_aspect_ratio) {
      // Set widths to equal
      stretched_application_surface_width = window_width;
      stretched_application_surface_height = height * stretched_application_surface_width / width;
    } else {
      // Stretch over whole
      stretched_application_surface_width = window_width;
      stretched_application_surface_height = window_height;
    }
    return [
      (window_width - stretched_application_surface_width) / 2, 
      (window_height - stretched_application_surface_height) / 2, 
      stretched_application_surface_width, stretched_application_surface_height];
  case Stretching.FULL:
    return [0, 0, window_width, window_height];
  }
[/CODE
 

Fanatrick

Member
Simple tree structure implementation and depth first search (GM:S 2.3+)
GML:
function ds_treenode() constructor {
    
    #region Internal
    
    static traverse_list = ds_list_create();
    
    static __traverse_node = function() {
        ds_list_add(traverse_list, identity);
        var _size = ds_list_size(children);
        for(var i = 0; i < _size; i ++) {
            var _child = children[| i];
            _child.__traverse_node();
        }
    }
    
    static __cleanup_deep = function() {
        var _size = ds_list_size(children);
        for(var i = 0; i < _size; i ++) {
            var _child = children[| i];
            _child.__cleanup_deep();
        }
        cleanup();
    }
    
    #endregion
    
    identity = self;
    depth = 0;
    
    data = undefined;
    parent = undefined;
    children = ds_list_create();
    
    static add_child = function(_node) {
        /// @func add_child(node)
        ds_list_add(children, _node);
        _node.set_parent(identity);
        _node.depth = depth + 1;
        return _node;
    }
    
    static add_children = function(_node) {
        /// @func add_children(node, [node2], ...[noden])
        add_child(_node);
        for(var i = 1; i < argument_count; i++){
            add_child(argument[i]);
        }
    }
    
    static get_children = function() {
        return children;
    }
    
    static set_data = function(_data) {
        /// @func set_data(data)
        data = _data;
    }
    
    static get_data = function() {
        return data;
    }
    
    static set_parent = function(_parent) {
        /// @func set_parent(parent)
        parent = _parent;
    }
    
    static get_parent = function() {
        return parent;
    }
    
    static get_identity = function() {
        return self.identity;
    }
    
    static traverse = function() {
        var _list = ds_list_create();
        __traverse_node();
        ds_list_copy(_list, traverse_list);
        ds_list_clear(traverse_list);
        return _list;
    }
    
    static get_root = function() {
        var _par = parent,
            _id = identity;
        while (_par != undefined) {
            _id = parent;
            _par = _id.parent;
        }
        return _id;
    }
    
    static cleanup = function() {
        ds_list_destroy(children);
        delete identity;
    }
    
    static cleanup_tree = function() {
        var _root = get_root();
        _root.__cleanup_deep();
    }
    
}
 

Juju

Member
Not really worth its own forum topic, but I made a library for handling structs and arrays (and nested variations thereof) for 2.3.0: https://github.com/JujuAdams/SNAP

Functions are:
1. snap_to_json_string(struct/array, [pretty], [alphabetizeStructs])
Turns nested structs/arrays into a JSON string. Can be prettified, and structs alphabetised by key, as desired

2. snap_from_json_string(string)
Parses a JSON string into nested structs/arrays

3. snap_to_binary(struct/array)
Serialises structs/arrays into a binary stream. Handy for multiplayer perhaps?

4. snap_from_binary(buffer, [offset], [size], [destroyBuffer])
Deserialises the above

5. foreach(struct/array, function)
Iterates over every member of a struct/array and executes a function for them. This is not a deep operation, though obviously you can recursively call foreach() inside the function to achieve this

6. snap_deep_copy(struct/array)
Performs a deep copy on a struct/array. No idea why YYG haven't implemented this already but here we are
 

Samuel Venable

Time Killer
Hey guys!

So...

I've previously tested my "Process Information" code on all platforms except macOS....

And after I tested it on Mac for the first time today, I noticed, (and fixed), some major bugs I found with it.

The post has been updated, click here to see the updated post: #42

Here's some screenshots of a test program I wrote which uses features from that code:

Win32:



macOS:



Linux:



BSD:



The images above shows process information given from the window that had focus when the screenshot was taken.

So basically this will be available for download soon on the marketplace and itch.io for those who are interested.
 
Last edited:

rytan451

Member
These are a collection of scripts that (relatively) quickly return how far an object can move in a specified direction without knocking into an object. Though it doesn't cover the case where a really fast object pretty much teleports through objects, it does cover all other cases that are likely to occur in a 2D game.

There's actually a very important reason why I'm using instances rather than data structures like structs. This reason is simple: GMS2 uses a data structure called an R-Tree to speed up collision checks. Instances are automatically put into the R-Tree, so I can tap into an R-Tree implementation using collision_rectangle_list without having to actually implement one. I believe the speedup associated with using the R-Tree justifies any slowdown caused by having lots of instances. Of course, if this does become a bottleneck, I'd likely have to make my own R-Tree implementation – and thank goodness that structs will be a thing for everyone by then!

GML:
/// @func move_aabb
/// @param x
/// @param y
/// @param width
/// @param height
/// @param dx
/// @param dy

var dx = argument4, dy = argument5;

if (abs(dx) > abs(dy)) {
  dx = _move_aabb_horizontal(argument0, argument1, argument2, argument3, argument4);
  dy = _move_aabb_vertical(argument0, argument1, argument2, argument3, argument5);
} else {
  dy = _move_aabb_vertical(argument0, argument1, argument2, argument3, argument5);
  dx = _move_aabb_horizontal(argument0, argument1, argument2, argument3, argument4);
}

return [dx, dy];

/// @func _move_aabb_horizontal
/// @param x
/// @param y
/// @param width
/// @param height
/// @param dx

if (argument4 == 0) {
  return 0;
}

var _bbleft, _bbright, _bbtop, _bbbottom, bbleft, bbright, bbtop, bbbottom, dx;
dx = argument4;
var involved_aabbs = aabb_intersecting_rectangle(argument0 + min(0, dx), argument1, argument2 + dx, argument3);
bbleft = argument0;
bbright = argument0 + argument2;
bbtop = argument1;
bbbottom = argument1 + argument3;

var l = ds_list_size(involved_aabbs), inst;
for (var i = 0; i < l; i++) {
  inst = involved_aabbs[| i];
  _bbleft = inst.x;
  _bbtop = inst.y;
  _bbright = inst.x + inst.width;
  _bbbottom = inst.y + inst.height;
  if (rectangle_in_rectangle(bbleft, bbtop, bbright, bbbottom, _bbleft + margin, _bbtop + margin, _bbright - margin, _bbbottom - margin) == 0 &&
      rectangle_in_rectangle(bbleft + dx, bbtop, bbright + dx, bbbottom, _bbleft + margin, _bbtop + margin, _bbright - margin, _bbbottom - margin) != 0) {
    if (dx > 0) {
      dx = _bbleft - bbright;
    } else {
      dx = _bbright - bbleft;
    }
  }
}

ds_list_destroy(involved_aabbs);

return dx;

/// @func _move_aabb_vertical
/// @param x
/// @param y
/// @param width
/// @param height
/// @param dy

if (argument4 == 0) {
  return 0;
}

var _bbleft, _bbright, _bbtop, _bbbottom, bbleft, bbright, bbtop, bbbottom, dy;
dy = argument4;
var involved_aabbs = aabb_intersecting_rectangle(argument0, argument1 + min(0, dy), argument2, argument3 + dy);
bbleft = argument0;
bbright = argument0 + argument2;
bbtop = argument1;
bbbottom = argument1 + argument3;

var l = ds_list_size(involved_aabbs), inst;
for (var i = 0; i < l; i++) {
  inst = involved_aabbs[| i];
  _bbleft = inst.x;
  _bbtop = inst.y;
  _bbright = inst.x + inst.width;
  _bbbottom = inst.y + inst.height;
  if (rectangle_in_rectangle(bbleft, bbtop, bbright, bbbottom, _bbleft + margin, _bbtop + margin, _bbright - margin, _bbbottom - margin) == 0 &&
      rectangle_in_rectangle(bbleft, bbtop + dy, bbright, bbbottom + dy, _bbleft + margin, _bbtop + margin, _bbright - margin, _bbbottom - margin) != 0) {
    if (dy > 0) {
      dy = _bbtop - bbbottom;
    } else {
      dy = _bbbottom - bbtop;
    }
  }
}
return dy;

/// @func aabb_intersecting_rectangle
/// @param x
/// @param y
/// @param width
/// @param height

var l = ds_list_create();
collision_rectangle_list(argument0, argument1, argument0 + argument2, argument1 + argument3, AABB, false, false, l, false);
return l;
EDIT: I was doing some rubber duck debugging, and I've thought up some ways to fix some of the limitations of the code.
 
Last edited:

Samuel Venable

Time Killer
Here's an example of how to run an external program whose main window will stay on top of the game window like a modal dialog.

(Asynchronously)

You can also read output from the program you run.

Required extension: Process Information

Works on Windows, Mac, and if the external program has the _NET_WM_PID atom set properly, it should work on Linux as well. But not all Linux applications will work with it for that reason, because not all Linux apps define that atom. GameMaker games currently do not for example, so you can't run an external game maker game on Linux and expect this to work on that platform.

First, create a GML script named "wait_for_child_of_ppid_to_exist"

The contents of that script, (don't worry about trying to wrap your head around what it does):
GML:
var pid  = argument0;
var ppid = argument1;

if (!pid_exists(pid) || !pid_exists(ppid)) {
  return false;
}

if (pid == ppid) {
  return false;
}

while (pid != ppid) {
  if (pid <= 1) break;
  pid = ppid_from_pid(pid);
}

return (pid == ppid);
Then create another script, "run_executable_modal_async"

The contents, and again, don't worry about trying to comprehend it if you don't get it:
GML:
process_execute_async("\"" + string(argument0) + "\" " + string(argument1));
while (!wait_for_child_of_ppid_to_exist(pid_from_top(), pid_from_self())) { }
wid_set_pwid(wid_from_top(), wid_from_window(window_handle()));
Now for an example

Create Event
GML:
globalvar previous_output = "";
// takes two arguments, first argument is the executable path, the second is the command line arguments string.
run_executable_modal_async("C:\\Windows\\System32\\notepad.exe", "\"" + working_directory + "hi fam.txt\"");
Notepad doesn't produce any output, but let's pretend it does for a moment.

If you wanted to get the output when the program is closed, do the following

Step event:
GML:
var pid = process_previous();
var out = process_evaluate();

if (pid != 0) { // if application terminated
  if (out != "") { // if output exists
    previous_output = out; // store output in previous_ouptut
    show_debug_message("<==:READ OUTPUT:==>\n" + previous_output);
    // set out variable back to an empty string ""
    process_clear_out();
  }
  // set pid variable back to zero
  process_clear_pid();
}
You will get an infinite loop for applications that you run which create no window, and this also goes for all programs that only open a terminal or command prompt, the terminal and command prompt will be hidden, so it won't work with those. Only GUI applications with a visible windows on creation.
 
Last edited:
Top