This commit is contained in:
Thomas Peterson 2025-11-27 13:22:59 +01:00
parent cb148dfb7c
commit e617930ca4
20 changed files with 823 additions and 53 deletions

View File

@ -36,8 +36,11 @@ class App
// Status label (referenced by tabs)
$statusLabel = new Label(
text: 'Fenster: ' . $this->window->getViewport()->windowWidth . 'x' . $this->window->getViewport()->windowHeight,
style: 'basis-4/8 text-black'
text: 'Fenster: ' .
$this->window->getViewport()->windowWidth .
'x' .
$this->window->getViewport()->windowHeight,
style: 'basis-4/8 text-black',
);
// Settings variables (simple variables work better with async than object properties)
@ -50,7 +53,13 @@ class App
$mainContainer->addComponent($menuBar);
// Create settings modal with the real menu bar
$settingsModal = new SettingsModal($this->settings, $menuBar, $currentApiKey, $currentPrivateKeyPath, $currentRemoteStartDir);
$settingsModal = new SettingsModal(
$this->settings,
$menuBar,
$currentApiKey,
$currentPrivateKeyPath,
$currentRemoteStartDir,
);
$mainContainer->addComponent($settingsModal->getModal());
// Build menu bar menus after modal is created
@ -69,6 +78,7 @@ class App
$this->settings,
$kanbanTab,
);
$kanbanTab->setServerListTab($serverListTab);
$sftpManagerTab = new SftpManagerTab(
$currentApiKey,
$currentPrivateKeyPath,
@ -97,10 +107,21 @@ class App
$statusBar->addSegment(new Label('v1.0', 'basis-1/8 text-center text-black border-l border-gray-300'));
$statusBar->addSegment(new Label(
'PHPNative Framework',
'basis-3/8 text-right text-black pr-2 border-l border-gray-300'
'basis-3/8 text-right text-black pr-2 border-l border-gray-300',
));
$mainContainer->addComponent($statusBar);
// Tray disabled for now due to GTK/XKB issues with static builds
// TODO: Re-enable after rebuilding static PHP with new SDL3 tray implementation
if (function_exists('tray_setup')) {
try {
tray_setup('', ['Beenden']);
} catch (\Throwable $e) {
// Tray initialization failed, continue without tray
error_log('Tray setup failed: ' . $e->getMessage());
}
}
// Set window content and run
$this->window->setRoot($mainContainer);
$this->app->addWindow($this->window);

View File

@ -24,6 +24,7 @@ class KanbanTab
private Container $editBoardButtonsContainer;
private string $currentEditingBoard = 'neu';
private null|string $currentEditingTaskId = null;
private null|ServerListTab $serverListTab = null;
public function __construct(Settings $settings)
{
@ -85,6 +86,11 @@ class KanbanTab
$this->renderBoards();
}
public function setServerListTab(ServerListTab $serverListTab): void
{
$this->serverListTab = $serverListTab;
}
public function getContainer(): Container
{
return $this->tab;
@ -270,6 +276,10 @@ class KanbanTab
$this->editModal->setVisible(false);
$this->renderBoards();
if ($this->serverListTab !== null) {
$this->serverListTab->refreshCurrentServerTasks();
}
}
private function renderBoards(): void
@ -360,6 +370,10 @@ class KanbanTab
$kanbanTab->settings->set('kanban.tasks', $tasks);
$kanbanTab->settings->save();
$kanbanTab->renderBoards();
if ($kanbanTab->serverListTab !== null) {
$kanbanTab->serverListTab->refreshCurrentServerTasks();
}
});
$headerRow->addComponent($editButton);

View File

@ -1032,4 +1032,9 @@ class ServerListTab
$this->renderTodoList();
}
public function refreshCurrentServerTasks(): void
{
$this->loadServerTasks();
}
}

View File

@ -2,6 +2,8 @@
declare(strict_types=1);
putenv('XKB_CONFIG_ROOT=/usr/share/X11/xkb');
putenv('XLOCALEDIR=/usr/share/X11/locale');
// Bootstrap: Load composer autoloader
require_once __DIR__ . '/../vendor/autoload.php';

View File

@ -36,11 +36,7 @@ $loadTasks = static function (string $path): array {
};
$saveTasks = static function (string $path, array $tasks): void {
file_put_contents(
$path,
json_encode($tasks, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
LOCK_EX,
);
file_put_contents($path, json_encode($tasks, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), LOCK_EX);
};
$tasks = $loadTasks($storagePath);
@ -54,7 +50,10 @@ $main = new Container('flex flex-col bg-gray-100 gap-4 p-4 h-full w-full');
$title = new Label('Todo Liste', 'text-2xl font-bold text-black');
$main->addComponent($title);
$input = new TextInput('Neue Aufgabe hinzufügen …', 'flex-1 border border-gray-300 rounded px-3 py-2 bg-white text-black');
$input = new TextInput(
'Neue Aufgabe hinzufügen …',
'flex-1 border border-gray-300 rounded px-3 py-2 bg-white text-black',
);
$addButton = new Button('Hinzufügen', 'px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700');
$inputRow = new Container('flex flex-row gap-3 w-full');
$inputRow->addComponent($input);
@ -78,10 +77,10 @@ $renderTasks = function () use (&$tasks, $listContainer, $statusLabel, $storageP
}
foreach ($tasks as $index => $task) {
$row = new Container('flex flex-row items-center gap-3 w-full border border-gray-200 rounded px-3 py-2 bg-white shadow-sm');
$taskLabelStyles = $task['done']
? 'flex-1 text-gray-500 line-through'
: 'flex-1 text-black';
$row = new Container(
'flex flex-row items-center gap-3 w-full border border-gray-200 rounded px-3 py-2 bg-white shadow-sm',
);
$taskLabelStyles = $task['done'] ? 'flex-1 text-gray-500 line-through' : 'flex-1 text-black';
$taskLabel = new Label($task['title'], $taskLabelStyles);
$row->addComponent($taskLabel);
@ -92,7 +91,14 @@ $renderTasks = function () use (&$tasks, $listContainer, $statusLabel, $storageP
: 'px-3 py-1 text-sm bg-emerald-500 text-white rounded hover:bg-emerald-600',
);
$toggleButton->setOnClick(function () use (&$tasks, $task, $storagePath, $saveTasks, $statusLabel, $renderTasks) {
$toggleButton->setOnClick(function () use (
&$tasks,
$task,
$storagePath,
$saveTasks,
$statusLabel,
$renderTasks,
) {
foreach ($tasks as &$entry) {
if ($entry['id'] === $task['id']) {
$entry['done'] = !$entry['done'];
@ -105,12 +111,17 @@ $renderTasks = function () use (&$tasks, $listContainer, $statusLabel, $storageP
$renderTasks();
});
$deleteButton = new Button(
'Löschen',
'px-3 py-1 text-sm bg-red-500 text-white rounded hover:bg-red-600',
);
$deleteButton = new Button('Löschen', 'px-3 py-1 text-sm bg-red-500 text-white rounded hover:bg-red-600');
$deleteButton->setOnClick(function () use (&$tasks, $index, $task, $storagePath, $saveTasks, $statusLabel, $renderTasks) {
$deleteButton->setOnClick(function () use (
&$tasks,
$index,
$task,
$storagePath,
$saveTasks,
$statusLabel,
$renderTasks,
) {
array_splice($tasks, $index, 1);
$saveTasks($storagePath, $tasks);
$statusLabel->setText('Aufgabe entfernt: ' . $task['title']);
@ -147,4 +158,64 @@ $addButton->setOnClick(function () use (&$tasks, $input, $saveTasks, $storagePat
$window->setRoot($main);
$app->addWindow($window);
// Setup Tray with callbacks
if (function_exists('tray_setup')) {
try {
tray_setup('', [
[
'label' => 'Neue Aufgabe',
'callback' => function ($idx) use (
&$tasks,
$input,
$saveTasks,
$storagePath,
$statusLabel,
$renderTasks,
) {
try {
error_log('Neue Aufgabe Callback called!');
$title = 'Tray: Neue Aufgabe';
error_log("Adding task: {$title}");
$tasks[] = [
'id' => uniqid('task_', true),
'title' => $title,
'done' => false,
];
error_log('Saving tasks...');
$saveTasks($storagePath, $tasks);
error_log('Updating UI...');
$statusLabel->setText('Aufgabe hinzugefügt: ' . $title);
$renderTasks();
error_log('Done!');
} catch (\Throwable $e) {
error_log('ERROR in Neue Aufgabe callback: ' . $e->getMessage());
error_log('Trace: ' . $e->getTraceAsString());
}
},
],
[
'label' => 'Fenster anzeigen',
'callback' => function ($idx) use ($window) {
// Show/focus window (TODO: implement window focus/show API)
},
],
[
'label' => 'Beenden',
'callback' => function ($idx) use ($app) {
$app->quit();
},
],
]);
} catch (\Throwable $e) {
error_log('Tray setup failed: ' . $e->getMessage());
}
}
$app->run();

View File

@ -1,12 +1,7 @@
[
{
"id": "task_69164e23f0d356.41043316",
"title": "Test",
"id": "task_692840bf6e9035.94502029",
"title": "Tray: Neue Aufgabe",
"done": false
},
{
"id": "task_69164e28dae205.72302890",
"title": "Geht",
"done": true
}
]

Binary file not shown.

Binary file not shown.

View File

@ -87,6 +87,8 @@ if test "$PHP_SDL3" != "no"; then
AC_MSG_WARN([libnotify not found via pkg-config, desktop_notify() will be disabled])
])
dnl SDL3 includes native tray support, no external dependencies needed
SDL_SOURCE_FILES="sdl3.c helper.c sdl3_image.c sdl3_ttf.c sdl3_events.c"
PHP_NEW_EXTENSION(sdl3, $SDL_SOURCE_FILES, $ext_shared)

View File

@ -3,7 +3,4 @@
# Created by configure
'./configure' \
'--with-sdl3' \
'--with-sdl3-image' \
'--with-sdl3-ttf' \
"$@"

View File

@ -413,7 +413,7 @@ $config_headers
Report bugs to the package provider."
ac_cs_config='--with-sdl3 --with-sdl3-image --with-sdl3-ttf'
ac_cs_config=''
ac_cs_version="\
config.status
configured by ./configure, generated by GNU Autoconf 2.72,
@ -494,7 +494,7 @@ if $ac_cs_silent; then
fi
if $ac_cs_recheck; then
set X /bin/bash './configure' '--with-sdl3' '--with-sdl3-image' '--with-sdl3-ttf' $ac_configure_extra_args --no-create --no-recursion
set X /bin/bash './configure' $ac_configure_extra_args --no-create --no-recursion
shift
\printf "%s\n" "running CONFIG_SHELL=/bin/bash $*" >&6
CONFIG_SHELL='/bin/bash'

37
php-sdl3/configure vendored
View File

@ -5247,6 +5247,7 @@ printf "%s\n" "#define HAVE_LIBNOTIFY 1" >>confdefs.h
fi
SDL_SOURCE_FILES="sdl3.c helper.c sdl3_image.c sdl3_ttf.c sdl3_events.c"
@ -6102,7 +6103,7 @@ ia64-*-hpux*)
;;
*-*-irix6*)
# Find out which ABI we are using.
echo '#line 6105 "configure"' > conftest.$ac_ext
echo '#line 6106 "configure"' > conftest.$ac_ext
if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
(eval $ac_compile) 2>&5
ac_status=$?
@ -7481,7 +7482,7 @@ else case e in #(
LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym"
cat > conftest.$ac_ext <<EOF
#line 7484 "configure"
#line 7485 "configure"
#include "confdefs.h"
int main(void) {
; return 0; }
@ -7643,11 +7644,11 @@ else case e in #(
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
-e 's:$: $lt_compiler_flag:'`
(eval echo "\"configure:7646: $lt_compile\"" >&5)
(eval echo "\"configure:7647: $lt_compile\"" >&5)
(eval "$lt_compile" 2>conftest.err)
ac_status=$?
cat conftest.err >&5
echo "configure:7650: \$? = $ac_status" >&5
echo "configure:7651: \$? = $ac_status" >&5
if (exit $ac_status) && test -s "$ac_outfile"; then
# The compiler can only warn and ignore the option if not recognized
# So say no if there are warnings other than the usual output.
@ -7943,11 +7944,11 @@ else case e in #(
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
-e 's:$: $lt_compiler_flag:'`
(eval echo "\"configure:7946: $lt_compile\"" >&5)
(eval echo "\"configure:7947: $lt_compile\"" >&5)
(eval "$lt_compile" 2>conftest.err)
ac_status=$?
cat conftest.err >&5
echo "configure:7950: \$? = $ac_status" >&5
echo "configure:7951: \$? = $ac_status" >&5
if (exit $ac_status) && test -s "$ac_outfile"; then
# The compiler can only warn and ignore the option if not recognized
# So say no if there are warnings other than the usual output.
@ -8051,11 +8052,11 @@ else case e in #(
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
-e 's:$: $lt_compiler_flag:'`
(eval echo "\"configure:8054: $lt_compile\"" >&5)
(eval echo "\"configure:8055: $lt_compile\"" >&5)
(eval "$lt_compile" 2>out/conftest.err)
ac_status=$?
cat out/conftest.err >&5
echo "configure:8058: \$? = $ac_status" >&5
echo "configure:8059: \$? = $ac_status" >&5
if (exit $ac_status) && test -s out/conftest2.$ac_objext
then
# The compiler can only warn and ignore the option if not recognized
@ -8516,7 +8517,7 @@ _LT_EOF
# Determine the default libpath from the value encoded in an empty executable.
cat > conftest.$ac_ext <<EOF
#line 8519 "configure"
#line 8520 "configure"
#include "confdefs.h"
int main(void) {
; return 0; }
@ -8558,7 +8559,7 @@ if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi
# Determine the default libpath from the value encoded in an empty executable.
cat > conftest.$ac_ext <<EOF
#line 8561 "configure"
#line 8562 "configure"
#include "confdefs.h"
int main(void) {
; return 0; }
@ -10139,7 +10140,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<EOF
#line 10142 "configure"
#line 10143 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
@ -10238,7 +10239,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<EOF
#line 10241 "configure"
#line 10242 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
@ -11307,7 +11308,7 @@ case $host_os in
# Determine the default libpath from the value encoded in an empty executable.
cat > conftest.$ac_ext <<EOF
#line 11310 "configure"
#line 11311 "configure"
#include "confdefs.h"
int main(void) {
; return 0; }
@ -11350,7 +11351,7 @@ if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi
# Determine the default libpath from the value encoded in an empty executable.
cat > conftest.$ac_ext <<EOF
#line 11353 "configure"
#line 11354 "configure"
#include "confdefs.h"
int main(void) {
; return 0; }
@ -12603,11 +12604,11 @@ else case e in #(
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
-e 's:$: $lt_compiler_flag:'`
(eval echo "\"configure:12606: $lt_compile\"" >&5)
(eval echo "\"configure:12607: $lt_compile\"" >&5)
(eval "$lt_compile" 2>conftest.err)
ac_status=$?
cat conftest.err >&5
echo "configure:12610: \$? = $ac_status" >&5
echo "configure:12611: \$? = $ac_status" >&5
if (exit $ac_status) && test -s "$ac_outfile"; then
# The compiler can only warn and ignore the option if not recognized
# So say no if there are warnings other than the usual output.
@ -12711,11 +12712,11 @@ else case e in #(
-e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
-e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
-e 's:$: $lt_compiler_flag:'`
(eval echo "\"configure:12714: $lt_compile\"" >&5)
(eval echo "\"configure:12715: $lt_compile\"" >&5)
(eval "$lt_compile" 2>out/conftest.err)
ac_status=$?
cat out/conftest.err >&5
echo "configure:12718: \$? = $ac_status" >&5
echo "configure:12719: \$? = $ac_status" >&5
if (exit $ac_status) && test -s out/conftest2.$ac_objext
then
# The compiler can only warn and ignore the option if not recognized

Binary file not shown.

View File

@ -11,11 +11,15 @@
#include "sdl3_events.h"
#include <SDL3/SDL.h>
#include <math.h>
#include <string.h>
#ifdef HAVE_LIBNOTIFY
#include <libnotify/notify.h>
#endif
// SDL3 native tray support
#include <SDL3/SDL_tray.h>
// Resource handles (nicht static, damit sie in anderen Modulen verfügbar sind)
int le_sdl_window;
int le_sdl_renderer;
@ -45,6 +49,64 @@ static void sdl_texture_dtor(zend_resource *rsrc) {
}
}
// --- Tray integration state ---
// SDL3 Tray globals
static SDL_Tray *g_sdl_tray = NULL;
static SDL_TrayMenu *g_sdl_tray_menu = NULL;
static SDL_TrayEntry **g_sdl_tray_entries = NULL;
static int g_sdl_tray_entry_count = 0;
static zval *g_tray_callbacks = NULL; // Array of PHP callbacks for each tray entry
static void SDLCALL php_tray_callback(void *userdata, SDL_TrayEntry *entry) {
intptr_t idx = (intptr_t)userdata;
idx = idx-1;
// Log all callback invocations for debugging
php_error_docref(NULL, E_NOTICE, "Tray callback invoked: userdata=%p (idx=%d), g_tray_callbacks=%p",
userdata, (int)idx, (void*)g_tray_callbacks);
// userdata can be NULL for events without callbacks (e.g., clicking the tray icon itself)
// This is normal, so just return silently
if (!userdata || !g_tray_callbacks) {
php_error_docref(NULL, E_NOTICE, "Tray callback: skipping (userdata=%p, callbacks=%p)",
userdata, (void*)g_tray_callbacks);
return;
}
// Check if we have a callback for this index
if (idx < 0 || idx >= g_sdl_tray_entry_count) {
php_error_docref(NULL, E_WARNING, "Tray callback: invalid index %d (max %d)", (int)idx, g_sdl_tray_entry_count);
return;
}
zval *callback = &g_tray_callbacks[idx];
// Only call if callback is set and callable
if (Z_TYPE_P(callback) == IS_UNDEF) {
php_error_docref(NULL, E_WARNING, "Tray callback %d: callback is undefined", (int)idx);
return;
}
if (!zend_is_callable(callback, 0, NULL)) {
php_error_docref(NULL, E_WARNING, "Tray callback %d: callback is not callable", (int)idx);
return;
}
zval retval;
zval params[1];
// Pass the index as parameter to the callback
ZVAL_LONG(&params[0], idx);
// Call the PHP callback
int result = call_user_function(EG(function_table), NULL, callback, &retval, 1, params);
if (result == SUCCESS) {
zval_ptr_dtor(&retval);
} else {
php_error_docref(NULL, E_WARNING, "Tray callback %d: call_user_function failed with code %d", (int)idx, result);
}
}
PHP_MINIT_FUNCTION(sdl3) {
le_sdl_window = zend_register_list_destructors_ex(sdl_window_dtor, NULL, "SDL_Window", module_number);
le_sdl_renderer = zend_register_list_destructors_ex(sdl_renderer_dtor, NULL, "SDL_Renderer", module_number);
@ -168,8 +230,10 @@ PHP_FUNCTION(sdl_get_window_id) {
PHP_FUNCTION(sdl_create_renderer) {
zval *win_res;
SDL_Window *win;
char *renderer_name = NULL;
size_t renderer_name_len = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|s", &win_res, &renderer_name, &renderer_name_len) == FAILURE) {
RETURN_THROWS();
}
@ -178,13 +242,32 @@ PHP_FUNCTION(sdl_create_renderer) {
RETURN_FALSE;
}
SDL_Renderer *ren = SDL_CreateRenderer(win, NULL);
SDL_Renderer *ren = SDL_CreateRenderer(win, renderer_name_len > 0 ? renderer_name : NULL);
if (!ren) {
RETURN_FALSE;
}
RETURN_RES(zend_register_resource(ren, le_sdl_renderer));
}
PHP_FUNCTION(sdl_get_num_render_drivers) {
int num = SDL_GetNumRenderDrivers();
RETURN_LONG(num);
}
PHP_FUNCTION(sdl_get_render_driver) {
zend_long index;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) {
RETURN_THROWS();
}
const char *name = SDL_GetRenderDriver((int)index);
if (!name) {
RETURN_FALSE;
}
RETURN_STRING(name);
}
PHP_FUNCTION(sdl_set_render_draw_color) {
zval *ren_res;
SDL_Renderer *ren;
@ -529,6 +612,211 @@ PHP_FUNCTION(sdl_set_texture_alpha_mod) {
RETURN_TRUE;
}
PHP_FUNCTION(tray_setup)
{
char *icon_path;
size_t icon_len;
zval *menu_arr = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|a", &icon_path, &icon_len, &menu_arr) == FAILURE) {
RETURN_THROWS();
}
// Clean up existing tray if any
if (g_sdl_tray) {
SDL_DestroyTray(g_sdl_tray);
g_sdl_tray = NULL;
g_sdl_tray_menu = NULL;
}
if (g_sdl_tray_entries) {
efree(g_sdl_tray_entries);
g_sdl_tray_entries = NULL;
g_sdl_tray_entry_count = 0;
}
if (g_tray_callbacks) {
// Free old callbacks
for (int i = 0; i < g_sdl_tray_entry_count; i++) {
zval_ptr_dtor(&g_tray_callbacks[i]);
}
efree(g_tray_callbacks);
g_tray_callbacks = NULL;
}
// Load icon if provided (optional)
SDL_Surface *icon_surface = NULL;
if (icon_len > 0) {
icon_surface = SDL_LoadBMP(icon_path);
// Icon can be NULL, SDL will handle it
}
// Initialize video subsystem if not already initialized (required for tray)
if (!SDL_WasInit(SDL_INIT_VIDEO)) {
if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {
if (icon_surface) {
SDL_DestroySurface(icon_surface);
}
php_error_docref(NULL, E_WARNING, "Failed to init video subsystem for tray: %s", SDL_GetError());
RETURN_FALSE;
}
}
// Check if DISPLAY is set (required for GTK-based tray on Linux)
#ifdef __linux__
const char *display = getenv("DISPLAY");
const char *wayland = getenv("WAYLAND_DISPLAY");
if (!display && !wayland) {
if (icon_surface) {
SDL_DestroySurface(icon_surface);
}
php_error_docref(NULL, E_WARNING, "Cannot create tray: No DISPLAY or WAYLAND_DISPLAY environment variable set");
RETURN_FALSE;
}
#endif
// Create SDL3 tray
g_sdl_tray = SDL_CreateTray(icon_surface, "PHP SDL3 Tray");
if (icon_surface) {
SDL_DestroySurface(icon_surface);
}
if (!g_sdl_tray) {
php_error_docref(NULL, E_WARNING, "Failed to create tray: %s", SDL_GetError());
RETURN_FALSE;
}
// Create tray menu
g_sdl_tray_menu = SDL_CreateTrayMenu(g_sdl_tray);
if (!g_sdl_tray_menu) {
SDL_DestroyTray(g_sdl_tray);
g_sdl_tray = NULL;
php_error_docref(NULL, E_WARNING, "Failed to create tray menu: %s", SDL_GetError());
RETURN_FALSE;
}
// Add menu items if provided
if (menu_arr && Z_TYPE_P(menu_arr) == IS_ARRAY) {
HashTable *ht = Z_ARRVAL_P(menu_arr);
int count = zend_hash_num_elements(ht);
if (count > 0) {
g_sdl_tray_entries = ecalloc(count, sizeof(SDL_TrayEntry *));
g_tray_callbacks = ecalloc(count, sizeof(zval));
g_sdl_tray_entry_count = count;
int idx = 0;
zval *val;
ZEND_HASH_FOREACH_VAL(ht, val) {
if (idx >= count) {
break;
}
const char *label = NULL;
zval *callback = NULL;
// Handle array entries: ['label' => '...', 'callback' => function]
if (Z_TYPE_P(val) == IS_ARRAY) {
zval *label_val = zend_hash_str_find(Z_ARRVAL_P(val), "label", sizeof("label") - 1);
zval *callback_val = zend_hash_str_find(Z_ARRVAL_P(val), "callback", sizeof("callback") - 1);
if (label_val && Z_TYPE_P(label_val) == IS_STRING) {
label = Z_STRVAL_P(label_val);
}
if (callback_val && zend_is_callable(callback_val, 0, NULL)) {
callback = callback_val;
}
}
// Handle simple string entries (backward compatibility)
else if (Z_TYPE_P(val) == IS_STRING) {
label = Z_STRVAL_P(val);
}
// Create entry (NULL label creates separator)
SDL_TrayEntry *entry = SDL_InsertTrayEntryAt(
g_sdl_tray_menu,
-1,
label,
SDL_TRAYENTRY_BUTTON
);
if (!entry) {
php_error_docref(NULL, E_WARNING, "Failed to create tray entry %d ('%s'): %s", idx, label ? label : "(null)", SDL_GetError());
}
if (entry && label) {
// Set callback with index+1 as userdata (so index 0 doesn't become NULL)
SDL_SetTrayEntryCallback(entry, php_tray_callback, (void *)(intptr_t)(idx + 1));
php_error_docref(NULL, E_NOTICE, "Registered tray entry %d: '%s' with callback=%s", idx, label, callback ? "YES" : "NO");
}
// Store PHP callback if provided
if (callback) {
ZVAL_COPY(&g_tray_callbacks[idx], callback);
} else {
ZVAL_UNDEF(&g_tray_callbacks[idx]);
}
g_sdl_tray_entries[idx] = entry;
idx++;
} ZEND_HASH_FOREACH_END();
}
}
RETURN_TRUE;
}
PHP_FUNCTION(tray_poll)
{
zend_bool blocking = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &blocking) == FAILURE) {
RETURN_THROWS();
}
if (!g_sdl_tray) {
RETURN_LONG(-1);
}
// SDL_UpdateTrays() processes events that were already polled by sdl_poll_event()
// The event polling happens in the Application loop before this is called
// SDL_UpdateTrays() will trigger our C callbacks, which call the PHP callbacks
SDL_UpdateTrays();
// Always return -1 (events are handled via callbacks)
RETURN_LONG(-1);
}
PHP_FUNCTION(tray_exit)
{
if (zend_parse_parameters_none() == FAILURE) {
RETURN_THROWS();
}
if (g_sdl_tray) {
SDL_DestroyTray(g_sdl_tray);
g_sdl_tray = NULL;
g_sdl_tray_menu = NULL;
}
if (g_sdl_tray_entries) {
efree(g_sdl_tray_entries);
g_sdl_tray_entries = NULL;
}
if (g_tray_callbacks) {
// Free callbacks
for (int i = 0; i < g_sdl_tray_entry_count; i++) {
zval_ptr_dtor(&g_tray_callbacks[i]);
}
efree(g_tray_callbacks);
g_tray_callbacks = NULL;
}
g_sdl_tray_entry_count = 0;
RETURN_TRUE;
}
PHP_FUNCTION(desktop_notify)
{
char *title, *body;
@ -1105,6 +1393,25 @@ PHP_FUNCTION(sdl_get_current_video_driver) {
RETURN_STRING(drv);
}
PHP_FUNCTION(sdl_get_num_video_drivers) {
int num = SDL_GetNumVideoDrivers();
RETURN_LONG(num);
}
PHP_FUNCTION(sdl_get_video_driver) {
zend_long index;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) {
RETURN_THROWS();
}
const char *driver = SDL_GetVideoDriver((int)index);
if (!driver) {
RETURN_FALSE;
}
RETURN_STRING(driver);
}
PHP_FUNCTION(sdl_start_text_input) {
zval *win_res;
SDL_Window *win;
@ -1167,6 +1474,14 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_create_renderer, 0, 0, 1)
ZEND_ARG_INFO(0, window)
ZEND_ARG_INFO(0, renderer_name)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_num_render_drivers, 0, 0, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_render_driver, 0, 0, 1)
ZEND_ARG_INFO(0, index)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_render_draw_color, 0, 0, 5)
@ -1242,6 +1557,18 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_texture_alpha_mod, 0, 0, 2)
ZEND_ARG_INFO(0, alpha)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_tray_setup, 0, 0, 1)
ZEND_ARG_INFO(0, icon)
ZEND_ARG_ARRAY_INFO(0, menuItems, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_tray_poll, 0, 0, 0)
ZEND_ARG_INFO(0, blocking)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_tray_exit, 0, 0, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_desktop_notify, 0, 0, 2)
ZEND_ARG_INFO(0, title)
ZEND_ARG_INFO(0, body)
@ -1329,6 +1656,14 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_current_video_driver, 0, 0, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_num_video_drivers, 0, 0, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_video_driver, 0, 0, 1)
ZEND_ARG_INFO(0, index)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_renderer_output_size, 0, 0, 1)
ZEND_ARG_INFO(0, renderer)
ZEND_END_ARG_INFO()
@ -1350,6 +1685,8 @@ const zend_function_entry sdl3_functions[] = {
PHP_FE(sdl_destroy_renderer, arginfo_sdl_destroy_renderer)
PHP_FE(sdl_get_window_id, arginfo_sdl_get_window_id)
PHP_FE(sdl_create_renderer, arginfo_sdl_create_renderer)
PHP_FE(sdl_get_num_render_drivers, arginfo_sdl_get_num_render_drivers)
PHP_FE(sdl_get_render_driver, arginfo_sdl_get_render_driver)
PHP_FE(sdl_set_render_draw_color, arginfo_sdl_set_render_draw_color)
PHP_FE(sdl_render_clear, arginfo_sdl_render_clear)
PHP_FE(sdl_render_fill_rect, arginfo_sdl_render_fill_rect)
@ -1365,6 +1702,11 @@ const zend_function_entry sdl3_functions[] = {
PHP_FE(sdl_update_texture, arginfo_sdl_update_texture)
PHP_FE(sdl_set_texture_blend_mode, arginfo_sdl_set_texture_blend_mode)
PHP_FE(sdl_set_texture_alpha_mod, arginfo_sdl_set_texture_alpha_mod)
// Tray API
PHP_FE(tray_setup, arginfo_tray_setup)
PHP_FE(tray_poll, arginfo_tray_poll)
PHP_FE(tray_exit, arginfo_tray_exit)
// Desktop notifications
PHP_FE(desktop_notify, arginfo_desktop_notify)
PHP_FE(sdl_create_box_shadow_texture, arginfo_sdl_create_box_shadow_texture)
PHP_FE(sdl_get_render_target, arginfo_sdl_get_render_target)
@ -1378,6 +1720,8 @@ const zend_function_entry sdl3_functions[] = {
PHP_FE(sdl_get_window_display_scale, arginfo_sdl_get_window_display_scale)
PHP_FE(sdl_get_display_content_scale, arginfo_sdl_get_display_content_scale)
PHP_FE(sdl_get_current_video_driver, arginfo_sdl_get_current_video_driver)
PHP_FE(sdl_get_num_video_drivers, arginfo_sdl_get_num_video_drivers)
PHP_FE(sdl_get_video_driver, arginfo_sdl_get_video_driver)
PHP_FE(sdl_get_renderer_output_size, arginfo_sdl_get_renderer_output_size)
PHP_FE(sdl_start_text_input, arginfo_sdl_start_text_input)
PHP_FE(sdl_stop_text_input, arginfo_sdl_stop_text_input)

View File

@ -86,6 +86,7 @@ class Application
while ($this->running && count($this->windows) > 0) {
$frameStart = microtime(true);
// Layout all windows FIRST (sets window references and calculates positions)
foreach ($this->windows as $windowId => $window) {
$window->layout();
@ -100,6 +101,12 @@ class Application
}
}
// Process tray events AFTER polling SDL events
// This ensures tray callbacks are triggered
if (function_exists('tray_poll')) {
tray_poll(false);
}
// Coalesce mouse motion events: Only keep the last MouseMotion event per window
// This dramatically reduces the number of events to process
$coalescedEvents = [];

70
test_render_drivers.php Normal file
View File

@ -0,0 +1,70 @@
<?php
// Test script for SDL_GetNumRenderDrivers and SDL_GetRenderDriver
echo "SDL3 Render Drivers Test\n";
echo "========================\n\n";
// Initialize SDL
if (!sdl_init(SDL_INIT_VIDEO)) {
die('Failed to initialize SDL: ' . sdl_get_error() . "\n");
}
echo "SDL initialized successfully\n\n";
// Get number of render drivers
$numDrivers = \sdl_get_num_render_drivers();
echo "Number of available render drivers: {$numDrivers}\n\n";
// List all available render drivers
echo "Available render drivers:\n";
for ($i = 0; $i < $numDrivers; $i++) {
$driver = sdl_get_render_driver($i);
if ($driver !== false) {
echo " [{$i}] {$driver}\n";
} else {
echo " [{$i}] Error getting driver\n";
}
}
echo "\n";
// Try creating a window and renderer
echo "Creating a window...\n";
$window = sdl_create_window('Render Driver Test', 800, 600, SDL_WINDOW_HIDDEN);
if (!$window) {
die('Failed to create window: ' . sdl_get_error() . "\n");
}
echo "Window created successfully\n\n";
// Try creating a renderer with default (null) name
echo "Attempting to create renderer with default settings...\n";
$renderer = sdl_create_renderer($window);
if (!$renderer) {
echo 'Failed to create renderer: ' . sdl_get_error() . "\n\n";
// Try with explicit driver names
if ($numDrivers > 0) {
echo "Trying with explicit driver names:\n";
for ($i = 0; $i < $numDrivers; $i++) {
$driverName = sdl_get_render_driver($i);
echo " Trying '{$driverName}'...\n";
$renderer = sdl_create_renderer($window, $driverName);
if ($renderer) {
echo " SUCCESS with '{$driverName}'\n";
sdl_destroy_renderer($renderer);
break;
} else {
echo ' FAILED: ' . sdl_get_error() . "\n";
}
}
}
} else {
echo "Renderer created successfully!\n";
sdl_destroy_renderer($renderer);
}
// Cleanup
sdl_destroy_window($window);
sdl_quit();
echo "\nTest completed!\n";

67
test_renderer_simple.php Normal file
View File

@ -0,0 +1,67 @@
<?php
putenv('XLOCALEDIR=/usr/share/X11/locale');
echo "Simple SDL3 Renderer Test\n";
echo "=========================\n\n";
// Initialize SDL
if (!sdl_init(SDL_INIT_VIDEO)) {
die('Failed to initialize SDL: ' . sdl_get_error() . "\n");
}
echo "SDL initialized successfully\n";
// Get current video driver
$videoDriver = sdl_get_current_video_driver();
echo 'Current video driver: ' . ($videoDriver ?: 'none') . "\n\n";
// Create window with different flags to test
$flags = SDL_WINDOW_HIDDEN;
echo "Creating window with HIDDEN flag...\n";
$window = sdl_create_window('Renderer Test', 800, 600, $flags);
if (!$window) {
die('Failed to create window: ' . sdl_get_error() . "\n");
}
echo "Window created successfully\n\n";
// Get video driver after window creation
$videoDriver = sdl_get_current_video_driver();
echo 'Video driver after window: ' . ($videoDriver ?: 'none') . "\n\n";
// Try to create renderer
echo "Attempting to create renderer...\n";
$renderer = sdl_create_renderer($window);
if (!$renderer) {
$error = sdl_get_error();
echo "FAILED to create renderer: {$error}\n\n";
// Try with a visible window instead
echo "Destroying window and trying with visible window...\n";
sdl_destroy_window($window);
$window = sdl_create_window('Renderer Test', 800, 600, 0);
if (!$window) {
die('Failed to create visible window: ' . sdl_get_error() . "\n");
}
echo "Visible window created\n";
echo "Attempting to create renderer on visible window...\n";
$renderer = sdl_create_renderer($window);
if (!$renderer) {
echo 'FAILED again: ' . sdl_get_error() . "\n";
} else {
echo "SUCCESS on visible window!\n";
sdl_destroy_renderer($renderer);
}
sdl_destroy_window($window);
} else {
echo "SUCCESS! Renderer created\n";
sdl_destroy_renderer($renderer);
sdl_destroy_window($window);
}
sdl_quit();
echo "\nTest completed\n";

76
test_simple_rect.php Normal file
View File

@ -0,0 +1,76 @@
<?php
// SDL initialisieren
if (!sdl_init(SDL_INIT_VIDEO)) {
die("sdl_init failed: " . sdl_get_error() . "\n");
}
echo "SDL initialized successfully\n";
// Fenster erstellen
$window = sdl_create_window(
"Simple Rectangle Test",
800, 600,
SDL_WINDOW_RESIZABLE
);
if (!$window) {
die("sdl_create_window failed: " . sdl_get_error() . "\n");
}
echo "Window created successfully\n";
// Renderer erstellen
$renderer = sdl_create_renderer($window, null);
if (!$renderer) {
sdl_destroy_window($window);
die("sdl_create_renderer failed: " . sdl_get_error() . "\n");
}
echo "Renderer created successfully\n";
// Event Loop
$running = true;
while ($running) {
// Events verarbeiten
while ($event = sdl_poll_event()) {
if ($event['type'] === SDL_EVENT_QUIT) {
$running = false;
} elseif ($event['type'] === SDL_EVENT_KEY_DOWN) {
if ($event['keycode'] === SDLK_ESCAPE || $event['keycode'] === SDLK_Q) {
$running = false;
}
}
}
// Bildschirm löschen (schwarz)
sdl_set_render_draw_color($renderer, 0, 0, 0, 255);
sdl_render_clear($renderer);
// Rotes Rechteck zeichnen (gefüllt)
sdl_set_render_draw_color($renderer, 255, 0, 0, 255);
sdl_render_fill_rect($renderer, ['x' => 300, 'y' => 200, 'w' => 200, 'h' => 150]);
// Grünes Rechteck zeichnen (Umriss)
sdl_set_render_draw_color($renderer, 0, 255, 0, 255);
sdl_render_rect($renderer, ['x' => 350, 'y' => 250, 'w' => 100, 'h' => 100]);
// Blaues Rechteck zeichnen (klein, gefüllt)
sdl_set_render_draw_color($renderer, 0, 0, 255, 255);
sdl_render_fill_rect($renderer, ['x' => 100, 'y' => 100, 'w' => 50, 'h' => 50]);
// Renderer anzeigen
sdl_render_present($renderer);
// Kleine Pause um CPU zu schonen
sdl_delay(16); // ~60 FPS
}
// Aufräumen
sdl_destroy_renderer($renderer);
sdl_destroy_window($window);
sdl_quit();
echo "Cleanup complete\n";

63
test_video_drivers.php Normal file
View File

@ -0,0 +1,63 @@
<?php
// Test script for SDL_GetNumVideoDrivers and SDL_GetVideoDriver
echo "SDL3 Video Drivers Test\n";
echo "========================\n\n";
// Initialize SDL
if (!sdl_init(SDL_INIT_VIDEO)) {
die("Failed to initialize SDL: " . sdl_get_error() . "\n");
}
echo "SDL initialized successfully\n\n";
// Get number of video drivers
$numDrivers = sdl_get_num_video_drivers();
echo "Number of available video drivers: $numDrivers\n\n";
// List all available video drivers
echo "Available video drivers:\n";
for ($i = 0; $i < $numDrivers; $i++) {
$driver = sdl_get_video_driver($i);
if ($driver !== false) {
echo " [$i] $driver\n";
} else {
echo " [$i] Error getting driver\n";
}
}
echo "\n";
// Get current video driver
$currentDriver = sdl_get_current_video_driver();
if ($currentDriver !== false) {
echo "Current video driver: $currentDriver\n";
} else {
echo "No video driver initialized yet (window not created)\n";
}
echo "\n";
// Create a window to initialize video driver
echo "Creating a window to initialize video driver...\n";
$window = sdl_create_window("Video Driver Test", 800, 600, SDL_WINDOW_HIDDEN);
if (!$window) {
die("Failed to create window: " . sdl_get_error() . "\n");
}
echo "Window created successfully\n\n";
// Now get the current driver again
$currentDriver = sdl_get_current_video_driver();
if ($currentDriver !== false) {
echo "Current video driver (after window creation): $currentDriver\n";
} else {
echo "Failed to get current video driver\n";
}
// Cleanup
sdl_destroy_window($window);
sdl_quit();
echo "\nTest completed successfully!\n";

35
test_window.php Normal file
View File

@ -0,0 +1,35 @@
<?php
// Fenster und Renderer erstellen
// In SDL3 ist ein Fenster standardmäßig sichtbar, daher verwenden wir 0 oder andere Flags
$window = sdl_create_window('Server Manager', 800, 600, 0);
$renderer = sdl_create_renderer($window, null);
// Event-Loop
$running = true;
while ($running) {
// Events verarbeiten
while ($event = sdl_poll_event()) {
if ($event['type'] === SDL_EVENT_QUIT) {
$running = false;
}
// Weitere Events (Tastatur, Maus, etc.)
}
// Rendern
sdl_set_render_draw_color($renderer, 0, 0, 0, 255);
sdl_render_clear($renderer);
// Dein UI-Code hier
sdl_render_present($renderer);
// Kleine Pause
sdl_delay(16); // ~60 FPS
}
// Cleanup
sdl_destroy_renderer($renderer);
sdl_destroy_window($window);
sdl_quit();